diff --git a/.changelog/4301.txt b/.changelog/4301.txt new file mode 100644 index 00000000000..8580d7c97ab --- /dev/null +++ b/.changelog/4301.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +logging: added plan time validation for `unique_writer_identity` on `google_logging_project_sink` +``` diff --git a/google/resource_logging_project_sink.go b/google/resource_logging_project_sink.go index bfe7c30ba4a..993738b906e 100644 --- a/google/resource_logging_project_sink.go +++ b/google/resource_logging_project_sink.go @@ -1,6 +1,8 @@ package google import ( + "context" + "errors" "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -10,11 +12,12 @@ const nonUniqueWriterAccount = "serviceAccount:cloud-logs@system.gserviceaccount func resourceLoggingProjectSink() *schema.Resource { schm := &schema.Resource{ - Create: resourceLoggingProjectSinkCreate, - Read: resourceLoggingProjectSinkRead, - Delete: resourceLoggingProjectSinkDelete, - Update: resourceLoggingProjectSinkUpdate, - Schema: resourceLoggingSinkSchema(), + Create: resourceLoggingProjectSinkCreate, + Read: resourceLoggingProjectSinkRead, + Delete: resourceLoggingProjectSinkDelete, + Update: resourceLoggingProjectSinkUpdate, + Schema: resourceLoggingSinkSchema(), + CustomizeDiff: resourceLoggingProjectSinkCustomizeDiff, Importer: &schema.ResourceImporter{ State: resourceLoggingSinkImportState("project"), }, @@ -61,6 +64,27 @@ func resourceLoggingProjectSinkCreate(d *schema.ResourceData, meta interface{}) return resourceLoggingProjectSinkRead(d, meta) } +// if bigquery_options is set unique_writer_identity must be true +func resourceLoggingProjectSinkCustomizeDiff(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + // separate func to allow unit testing + return resourceLoggingProjectSinkCustomizeDiffFunc(d) +} + +func resourceLoggingProjectSinkCustomizeDiffFunc(diff TerraformResourceDiff) error { + if !diff.HasChange("bigquery_options.#") { + return nil + } + + bigqueryOptions := diff.Get("bigquery_options.#").(int) + if bigqueryOptions > 0 { + uwi := diff.Get("unique_writer_identity") + if !uwi.(bool) { + return errors.New("unique_writer_identity must be true when bigquery_options is supplied") + } + } + return nil +} + func resourceLoggingProjectSinkRead(d *schema.ResourceData, meta interface{}) error { config := meta.(*Config) userAgent, err := generateUserAgentString(d, config.userAgent) diff --git a/google/resource_logging_project_sink_test.go b/google/resource_logging_project_sink_test.go index ef0f7891e0f..e24cb4729bf 100644 --- a/google/resource_logging_project_sink_test.go +++ b/google/resource_logging_project_sink_test.go @@ -185,6 +185,62 @@ func TestAccLoggingProjectSink_loggingbucket(t *testing.T) { }) } +func TestLoggingProjectSink_bigqueryOptionCustomizedDiff(t *testing.T) { + t.Parallel() + + type LoggingProjectSink struct { + BigqueryOptions int + UniqueWriterIdentity bool + } + cases := map[string]struct { + ExpectedError bool + After LoggingProjectSink + }{ + "no biquery options with false unique writer identity": { + ExpectedError: false, + After: LoggingProjectSink{ + BigqueryOptions: 0, + UniqueWriterIdentity: false, + }, + }, + "no biquery options with true unique writer identity": { + ExpectedError: false, + After: LoggingProjectSink{ + BigqueryOptions: 0, + UniqueWriterIdentity: true, + }, + }, + "biquery options with false unique writer identity": { + ExpectedError: true, + After: LoggingProjectSink{ + BigqueryOptions: 1, + UniqueWriterIdentity: false, + }, + }, + "biquery options with true unique writer identity": { + ExpectedError: false, + After: LoggingProjectSink{ + BigqueryOptions: 1, + UniqueWriterIdentity: true, + }, + }, + } + + for tn, tc := range cases { + d := &ResourceDiffMock{ + After: map[string]interface{}{ + "bigquery_options.#": tc.After.BigqueryOptions, + "unique_writer_identity": tc.After.UniqueWriterIdentity, + }, + } + err := resourceLoggingProjectSinkCustomizeDiffFunc(d) + hasError := err != nil + if tc.ExpectedError != hasError { + t.Errorf("%v: expected has error %v, but was %v", tn, tc.ExpectedError, hasError) + } + } +} + func testAccCheckLoggingProjectSinkDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { config := googleProviderConfig(t) diff --git a/website/docs/r/logging_project_sink.html.markdown b/website/docs/r/logging_project_sink.html.markdown index 16f416842f8..b1ebd700ad9 100644 --- a/website/docs/r/logging_project_sink.html.markdown +++ b/website/docs/r/logging_project_sink.html.markdown @@ -140,8 +140,8 @@ The following arguments are supported: * `unique_writer_identity` - (Optional) Whether or not to create a unique identity associated with this sink. If `false` (the default), then the `writer_identity` used is `serviceAccount:cloud-logs@system.gserviceaccount.com`. 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. + then a unique service account is created and used for this sink. If you wish to publish logs across projects or utilize + `bigquery_options`, you must set `unique_writer_identity` to true. * `bigquery_options` - (Optional) Options that affect sinks exporting data to BigQuery. Structure documented below.