Skip to content

Commit 5c0bac9

Browse files
modular-magicianrileykarsonmelinath
authored
fix issue #14893: add the customWriterIdentity parameter to logging_s… (#8995) (#16216)
* fix issue #14893: add the customWriterIdentity parameter to logging_sink resources * ignore the customer_writer_identity field in test case * Format the modified code and refactor the log client creation statement * update project sink creation request object with proper variable name * Update the sink resource documentation and remove the WriterIdentity field in update input * refactor logging project sink to have consistent request format between create and patch * Allow the test case to patch the customer_writer_identity field * Fix code comment typo based on code review * Add testCheckFuncs to validate the custom_writer_identity returned as Writer_identity output * gofmt the test case * Fix writer_identity TestCheckFunc in create test case * Fix writer_identity TestCheckFunc in update test case * Add headers for all Examples in logging sink document page * Update custom_writer_identity field description in project sink documentation * Remove uniqueWriterIdentity dependency for customerWriterIdentity in project logging sink documentation --------- [upstream:cd543bb71eb3c5e146678d93042aaa9580486569] Signed-off-by: Gang Chen <[email protected]> Signed-off-by: gangchen03 <[email protected]> Signed-off-by: Modular Magician <[email protected]> Co-authored-by: Riley Karson <[email protected]> Co-authored-by: Stephen Lewis (Burrows) <[email protected]>
1 parent dba09c1 commit 5c0bac9

File tree

4 files changed

+245
-4
lines changed

4 files changed

+245
-4
lines changed

.changelog/8995.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
logging: added `custom_writer_identity` field to `google_logging_project_sink`
3+
```

google/services/logging/resource_logging_project_sink.go

+31-3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ func ResourceLoggingProjectSink() *schema.Resource {
4343
Default: true,
4444
Description: `Whether or not to create a unique identity associated with this sink. If false (the legacy behavior), then the writer_identity used is serviceAccount:[email protected]. If true, then a unique service account is created and used for this sink. If you wish to publish logs across projects, you must set unique_writer_identity to true.`,
4545
}
46+
schm.Schema["custom_writer_identity"] = &schema.Schema{
47+
Type: schema.TypeString,
48+
Optional: true,
49+
Description: `A service account provided by the caller that will be used to write the log entries. The format must be serviceAccount:some@email. This field can only be specified if you are routing logs to a destination outside this sink's project. If not specified, a Logging service account will automatically be generated.`,
50+
}
4651
return schm
4752
}
4853

@@ -60,8 +65,20 @@ func resourceLoggingProjectSinkCreate(d *schema.ResourceData, meta interface{})
6065

6166
id, sink := expandResourceLoggingSink(d, "projects", project)
6267
uniqueWriterIdentity := d.Get("unique_writer_identity").(bool)
68+
customWriterIdentity := d.Get("custom_writer_identity").(string)
69+
70+
projectSinkCreateRequest := config.NewLoggingClient(userAgent).Projects.Sinks.Create(id.parent(), sink)
71+
72+
// if custom-sa is specified, use it to write log and it requires uniqueWriterIdentity to be set as well
73+
// otherwise set the uniqueWriter identity
74+
if customWriterIdentity != "" {
75+
projectSinkCreateRequest = projectSinkCreateRequest.UniqueWriterIdentity(uniqueWriterIdentity).CustomWriterIdentity(customWriterIdentity)
76+
} else {
77+
projectSinkCreateRequest = projectSinkCreateRequest.UniqueWriterIdentity(uniqueWriterIdentity)
78+
}
79+
80+
_, err = projectSinkCreateRequest.Do()
6381

64-
_, err = config.NewLoggingClient(userAgent).Projects.Sinks.Create(id.parent(), sink).UniqueWriterIdentity(uniqueWriterIdentity).Do()
6582
if err != nil {
6683
return err
6784
}
@@ -138,9 +155,20 @@ func resourceLoggingProjectSinkUpdate(d *schema.ResourceData, meta interface{})
138155

139156
sink, updateMask := expandResourceLoggingSinkForUpdate(d)
140157
uniqueWriterIdentity := d.Get("unique_writer_identity").(bool)
158+
customWriterIdentity := d.Get("custom_writer_identity").(string)
159+
160+
projectSinkUpdateRequest := config.NewLoggingClient(userAgent).Projects.Sinks.Patch(d.Id(), sink).UpdateMask(updateMask)
161+
162+
// if custom-sa is specified, use it to write log and it reqiures uniqueWriterIdentity to be set as well
163+
// otherwise set the uniqueWriter identity
164+
if customWriterIdentity != "" {
165+
projectSinkUpdateRequest = projectSinkUpdateRequest.UniqueWriterIdentity(uniqueWriterIdentity).CustomWriterIdentity(customWriterIdentity)
166+
} else {
167+
projectSinkUpdateRequest = projectSinkUpdateRequest.UniqueWriterIdentity(uniqueWriterIdentity)
168+
}
169+
170+
_, err = projectSinkUpdateRequest.Do()
141171

142-
_, err = config.NewLoggingClient(userAgent).Projects.Sinks.Patch(d.Id(), sink).
143-
UpdateMask(updateMask).UniqueWriterIdentity(uniqueWriterIdentity).Do()
144172
if err != nil {
145173
return err
146174
}

google/services/logging/resource_logging_project_sink_test.go

+159
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,57 @@ func TestAccLoggingProjectSink_updatePreservesUniqueWriter(t *testing.T) {
144144
})
145145
}
146146

147+
func TestAccLoggingProjectSink_updatePreservesCustomWriter(t *testing.T) {
148+
t.Parallel()
149+
150+
sinkName := "tf-test-sink-" + acctest.RandString(t, 10)
151+
account := "tf-test-sink-sa" + acctest.RandString(t, 10)
152+
accountUpdated := "tf-test-sink-sa" + acctest.RandString(t, 10)
153+
testProject := envvar.GetTestProjectFromEnv()
154+
155+
// custom_writer_identity is write-only, and writer_dietity is an output only field
156+
// verify that the value of writer_identity matches the expected custom_writer_identity.
157+
expectedWriterIdentity := fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", account, testProject)
158+
expectedUpdatedWriterIdentity := fmt.Sprintf("serviceAccount:%s@%s.iam.gserviceaccount.com", accountUpdated, testProject)
159+
160+
org := envvar.GetTestOrgFromEnv(t)
161+
billingId := envvar.GetTestBillingAccountFromEnv(t)
162+
project := fmt.Sprintf("tf-test-%d", acctest.RandInt(t))
163+
164+
acctest.VcrTest(t, resource.TestCase{
165+
PreCheck: func() { acctest.AccTestPreCheck(t) },
166+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
167+
CheckDestroy: testAccCheckLoggingProjectSinkDestroyProducer(t),
168+
Steps: []resource.TestStep{
169+
{
170+
Config: testAccLoggingProjectSink_customWriter(org, billingId, project, sinkName, account),
171+
Check: resource.ComposeTestCheckFunc(
172+
resource.TestCheckResourceAttr("google_logging_project_sink.custom_writer", "writer_identity", expectedWriterIdentity),
173+
),
174+
},
175+
{
176+
ResourceName: "google_logging_project_sink.custom_writer",
177+
ImportState: true,
178+
ImportStateVerify: true,
179+
// Logging sink create API doesn't return this field in response
180+
ImportStateVerifyIgnore: []string{"custom_writer_identity"},
181+
},
182+
{
183+
Config: testAccLoggingProjectSink_customWriterUpdated(org, billingId, project, sinkName, accountUpdated),
184+
Check: resource.ComposeTestCheckFunc(
185+
resource.TestCheckResourceAttr("google_logging_project_sink.custom_writer", "writer_identity", expectedUpdatedWriterIdentity),
186+
),
187+
},
188+
{
189+
ResourceName: "google_logging_project_sink.custom_writer",
190+
ImportState: true,
191+
ImportStateVerify: true,
192+
ImportStateVerifyIgnore: []string{"custom_writer_identity"},
193+
},
194+
},
195+
})
196+
}
197+
147198
func TestAccLoggingProjectSink_updateBigquerySink(t *testing.T) {
148199
t.Parallel()
149200

@@ -415,6 +466,114 @@ resource "google_storage_bucket" "gcs-bucket" {
415466
`, name, envvar.GetTestProjectFromEnv(), bucketName)
416467
}
417468

469+
func testAccLoggingProjectSink_customWriter(org, billingId, project, name, serviceAccount string) string {
470+
return fmt.Sprintf(`
471+
resource "google_project" "destination-project" {
472+
project_id = "%s"
473+
name = "%s"
474+
org_id = "%s"
475+
billing_account = "%s"
476+
}
477+
478+
resource "google_logging_project_bucket_config" "destination-bucket" {
479+
project = google_project.destination-project.project_id
480+
location = "us-central1"
481+
retention_days = 30
482+
bucket_id = "shared-bucket"
483+
}
484+
485+
resource "google_service_account" "test-account1" {
486+
account_id = "%s"
487+
display_name = "Log Sink Custom WriterIdentity Testing Account"
488+
}
489+
490+
resource "google_project_iam_member" "custom-sa-logbucket-binding" {
491+
project = google_project.destination-project.project_id
492+
role = "roles/logging.bucketWriter"
493+
member = "serviceAccount:${google_service_account.test-account1.email}"
494+
}
495+
496+
data "google_project" "testing_project" {
497+
project_id = "%s"
498+
}
499+
500+
locals {
501+
project_number = data.google_project.testing_project.number
502+
}
503+
504+
resource "google_service_account_iam_member" "loggingsa-customsa-binding" {
505+
service_account_id = google_service_account.test-account1.name
506+
role = "roles/iam.serviceAccountTokenCreator"
507+
member = "serviceAccount:service-${local.project_number}@gcp-sa-logging.iam.gserviceaccount.com"
508+
}
509+
510+
resource "google_logging_project_sink" "custom_writer" {
511+
name = "%s"
512+
destination = "logging.googleapis.com/projects/${google_project.destination-project.project_id}/locations/us-central1/buckets/shared-bucket"
513+
filter = "logName=\"projects/%s/logs/compute.googleapis.com%%2Factivity_log\" AND severity>=ERROR"
514+
515+
unique_writer_identity = true
516+
custom_writer_identity = "serviceAccount:${google_service_account.test-account1.email}"
517+
518+
depends_on = [google_logging_project_bucket_config.destination-bucket]
519+
}
520+
`, project, project, org, billingId, serviceAccount, envvar.GetTestProjectFromEnv(), name, envvar.GetTestProjectFromEnv())
521+
}
522+
523+
func testAccLoggingProjectSink_customWriterUpdated(org, billingId, project, name, serviceAccount string) string {
524+
return fmt.Sprintf(`
525+
resource "google_project" "destination-project" {
526+
project_id = "%s"
527+
name = "%s"
528+
org_id = "%s"
529+
billing_account = "%s"
530+
}
531+
532+
resource "google_logging_project_bucket_config" "destination-bucket" {
533+
project = google_project.destination-project.project_id
534+
location = "us-central1"
535+
retention_days = 30
536+
bucket_id = "shared-bucket"
537+
}
538+
539+
resource "google_service_account" "test-account2" {
540+
account_id = "%s"
541+
display_name = "Updated Log Sink Custom WriterIdentity Testing Account"
542+
}
543+
544+
resource "google_project_iam_member" "custom-sa-logbucket-binding" {
545+
project = google_project.destination-project.project_id
546+
role = "roles/logging.bucketWriter"
547+
member = "serviceAccount:${google_service_account.test-account2.email}"
548+
}
549+
550+
data "google_project" "testing_project" {
551+
project_id = "%s"
552+
}
553+
554+
locals {
555+
project_number = data.google_project.testing_project.number
556+
}
557+
558+
resource "google_service_account_iam_member" "loggingsa-customsa-binding" {
559+
service_account_id = google_service_account.test-account2.name
560+
role = "roles/iam.serviceAccountTokenCreator"
561+
member = "serviceAccount:service-${local.project_number}@gcp-sa-logging.iam.gserviceaccount.com"
562+
}
563+
564+
resource "google_logging_project_sink" "custom_writer" {
565+
name = "%s"
566+
destination = "logging.googleapis.com/projects/${google_project.destination-project.project_id}/locations/us-central1/buckets/shared-bucket"
567+
filter = "logName=\"projects/%s/logs/compute.googleapis.com%%2Factivity_log\" AND severity>=WARNING"
568+
569+
unique_writer_identity = true
570+
custom_writer_identity = "serviceAccount:${google_service_account.test-account2.email}"
571+
572+
depends_on = [google_logging_project_bucket_config.destination-bucket]
573+
}
574+
`, project, project, org, billingId, serviceAccount, envvar.GetTestProjectFromEnv(), name, envvar.GetTestProjectFromEnv())
575+
}
576+
418577
func testAccLoggingProjectSink_heredoc(name, project, bucketName string) string {
419578
return fmt.Sprintf(`
420579
resource "google_logging_project_sink" "heredoc" {

website/docs/r/logging_project_sink.html.markdown

+52-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Manages a project-level logging sink. For more information see:
1818

1919
~> **Note** You must [enable the Cloud Resource Manager API](https://console.cloud.google.com/apis/library/cloudresourcemanager.googleapis.com)
2020

21-
## Example Usage
21+
## Example Usage - Basic Sink
2222

2323
```hcl
2424
resource "google_logging_project_sink" "my-sink" {
@@ -35,6 +35,8 @@ resource "google_logging_project_sink" "my-sink" {
3535
}
3636
```
3737

38+
## Example Usage - Cloud Storage Bucket Destination
39+
3840
A more complete example follows: this creates a compute instance, as well as a log sink that logs all activity to a
3941
cloud storage bucket. Because we are using `unique_writer_identity`, we must grant it access to the bucket.
4042

@@ -89,6 +91,50 @@ resource "google_project_iam_binding" "gcs-bucket-writer" {
8991
}
9092
```
9193

94+
## Example Usage - User-managed Service Account
95+
96+
The following example creates a sink that are configured with user-managed service accounts, by specifying
97+
the `custom_writer_identity` field.
98+
99+
Note that you can only create a sink that uses a user-managed service account when the sink destination
100+
is a log bucket.
101+
102+
```hcl
103+
resource "google_service_account" "custom-sa" {
104+
project = "other-project-id"
105+
account_id = "gce-log-bucket-sink"
106+
display_name = "gce-log-bucket-sink"
107+
}
108+
109+
# Create a sink that uses user-managed service account
110+
resource "google_logging_project_sink" "my-sink" {
111+
name = "other-project-log-bucket-sink"
112+
113+
# Can export to log bucket in another project
114+
destination = "logging.googleapis.com/projects/other-project-id/locations/global/buckets/gce-logs"
115+
116+
# Log all WARN or higher severity messages relating to instances
117+
filter = "resource.type = gce_instance AND severity >= WARNING"
118+
119+
unique_writer_identity = true
120+
121+
# Use a user-managed service account
122+
custom_writer_identity = google_service_account.custom-sa.email
123+
}
124+
125+
# grant writer access to the user-managed service account
126+
resource "google_project_iam_member" "custom-sa-logbucket-binding" {
127+
project = "destination-project-id"
128+
role = "roles/logging.bucketWriter"
129+
member = "serviceAccount:${google_service_account.custom-sa.email}"
130+
}
131+
```
132+
133+
The above example will create a log sink that route logs to destination GCP project using
134+
an user-managed service account.
135+
136+
## Example Usage - Sink Exclusions
137+
92138
The following example uses `exclusions` to filter logs that will not be exported. In this example logs are exported to a [log bucket](https://cloud.google.com/logging/docs/buckets) and there are 2 exclusions configured
93139

94140
```hcl
@@ -147,6 +193,11 @@ The following arguments are supported:
147193
then a unique service account is created and used for this sink. If you wish to publish logs across projects or utilize
148194
`bigquery_options`, you must set `unique_writer_identity` to true.
149195

196+
* `custom_writer_identity` - (Optional) A user managed service account that will be used to write
197+
the log entries. The format must be `serviceAccount:some@email`. This field can only be specified if you are
198+
routing logs to a destination outside this sink's project. If not specified, a Logging service account
199+
will automatically be generated.
200+
150201
* `bigquery_options` - (Optional) Options that affect sinks exporting data to BigQuery. Structure [documented below](#nested_bigquery_options).
151202

152203
* `exclusions` - (Optional) Log entries that match any of the exclusion filters will not be exported. If a log entry is matched by both `filter` and one of `exclusions.filter`, it will not be exported. Can be repeated multiple times for multiple exclusions. Structure is [documented below](#nested_exclusions).

0 commit comments

Comments
 (0)