From ad8e6cbf04da50ac8be5e1119b4a6b9347130d20 Mon Sep 17 00:00:00 2001 From: Daniel Andrade Date: Tue, 1 Oct 2024 19:09:26 -0300 Subject: [PATCH] Add create_ignore_already_exists to google_sourcerepo_repository (#11770) Co-authored-by: Zhenhua Li --- mmv1/products/sourcerepo/Repository.yaml | 4 + .../constants/source_repo_repository.go.tmpl | 44 ++++++++ .../source_repo_repository.go.tmpl | 100 ++++++++++++++++++ .../source_repo_repository.tmpl | 6 ++ .../resource_sourcerepo_repository_test.go | 93 ++++++++++++++++ 5 files changed, 247 insertions(+) create mode 100644 mmv1/templates/terraform/custom_create/source_repo_repository.go.tmpl create mode 100644 mmv1/templates/terraform/extra_schema_entry/source_repo_repository.tmpl diff --git a/mmv1/products/sourcerepo/Repository.yaml b/mmv1/products/sourcerepo/Repository.yaml index dbc173a73200..825c7be24376 100644 --- a/mmv1/products/sourcerepo/Repository.yaml +++ b/mmv1/products/sourcerepo/Repository.yaml @@ -20,6 +20,8 @@ references: 'Official Documentation': 'https://cloud.google.com/source-repositories/' api: 'https://cloud.google.com/source-repositories/docs/reference/rest/v1/projects.repos' docs: + optional_properties: | + * `create_ignore_already_exists` - (Optional) If set to true, skip repository creation if a repository with the same name already exists. base_url: 'projects/{{project}}/repos' self_link: 'projects/{{project}}/repos/{{name}}' update_verb: 'PATCH' @@ -41,6 +43,8 @@ custom_code: constants: 'templates/terraform/constants/source_repo_repository.go.tmpl' update_encoder: 'templates/terraform/update_encoder/source_repo_repository.tmpl' post_create: 'templates/terraform/post_create/source_repo_repository_update.go.tmpl' + extra_schema_entry: templates/terraform/extra_schema_entry/source_repo_repository.tmpl + custom_create: templates/terraform/custom_create/source_repo_repository.go.tmpl exclude_tgc: true examples: - name: 'sourcerepo_repository_basic' diff --git a/mmv1/templates/terraform/constants/source_repo_repository.go.tmpl b/mmv1/templates/terraform/constants/source_repo_repository.go.tmpl index f4dfd14cf77a..4684dec4c47e 100644 --- a/mmv1/templates/terraform/constants/source_repo_repository.go.tmpl +++ b/mmv1/templates/terraform/constants/source_repo_repository.go.tmpl @@ -26,3 +26,47 @@ func resourceSourceRepoRepositoryPubSubConfigsHash(v interface{}) int { return tpgresource.Hashcode(buf.String()) } + +func resourceSourceRepoRepositoryPollRead(d *schema.ResourceData, meta interface{}) transport_tpg.PollReadFunc { + return func() (map[string]interface{}, error) { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return nil, err + } + + url, err := tpgresource.ReplaceVars(d, config, "{{"{{SourceRepoBasePath}}projects/{{project}}/repos"}}") + if err != nil { + return nil, err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return nil, fmt.Errorf("error fetching project for Repository: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + // Confirm the source repository exists + headers := make(http.Header) + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Headers: headers, + }) + + if err != nil { + return nil, err + } + return nil, nil + } +} diff --git a/mmv1/templates/terraform/custom_create/source_repo_repository.go.tmpl b/mmv1/templates/terraform/custom_create/source_repo_repository.go.tmpl new file mode 100644 index 000000000000..5332b58c17b9 --- /dev/null +++ b/mmv1/templates/terraform/custom_create/source_repo_repository.go.tmpl @@ -0,0 +1,100 @@ +userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) +if err != nil { + return err +} + +obj := make(map[string]interface{}) +nameProp, err := expandSourceRepoRepositoryName(d.Get("name"), d, config) +if err != nil { + return err +} else if v, ok := d.GetOkExists("name"); !tpgresource.IsEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp +} +pubsubConfigsProp, err := expandSourceRepoRepositoryPubsubConfigs(d.Get("pubsub_configs"), d, config) +if err != nil { + return err +} else if v, ok := d.GetOkExists("pubsub_configs"); !tpgresource.IsEmptyValue(reflect.ValueOf(pubsubConfigsProp)) && (ok || !reflect.DeepEqual(v, pubsubConfigsProp)) { + obj["pubsubConfigs"] = pubsubConfigsProp +} + +url, err := tpgresource.ReplaceVars(d, config, "{{"{{SourceRepoBasePath}}projects/{{project}}/repos"}}") +if err != nil { + return err +} + +log.Printf("[DEBUG] Creating new Repository: %#v", obj) +billingProject := "" + +project, err := tpgresource.GetProject(d, config) +if err != nil { + return fmt.Errorf("Error fetching project for Repository: %s", err) +} +billingProject = project + +// err == nil indicates that the billing_project value was found +if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp +} + +headers := make(http.Header) +res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutCreate), + Headers: headers, +}) +if err != nil { + gerr, ok := err.(*googleapi.Error) + alreadyExists := ok && gerr.Code == 409 && d.Get("create_ignore_already_exists").(bool) + if alreadyExists { + log.Printf("[DEBUG] Calling get Repository after already exists error") + res, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Headers: headers, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("SourceRepoRepository %q", d.Id())) + } + } else { + return fmt.Errorf("Error creating Repository: %s", err) + } +} + + +// We poll until the resource is found due to eventual consistency issue +// on part of the api https://cloud.google.com/iam/docs/overview#consistency +err = transport_tpg.PollingWaitTime(resourceSourceRepoRepositoryPollRead(d, meta), transport_tpg.PollCheckForExistence, "Creating Source Repository", d.Timeout(schema.TimeoutCreate), 1) + +if err != nil { + return err +} + +// We can't guarantee complete consistency even after polling, +// so sleep for some additional time to reduce the likelihood of +// eventual consistency failures. +time.Sleep(10 * time.Second) + +// Store the ID now +id, err := tpgresource.ReplaceVars(d, config, "{{"projects/{{project}}/repos/{{name}}"}}") +if err != nil { + return fmt.Errorf("Error constructing id: %s", err) +} +d.SetId(id) + +if v, ok := d.GetOkExists("pubsub_configs"); !tpgresource.IsEmptyValue(reflect.ValueOf(pubsubConfigsProp)) && (ok || !reflect.DeepEqual(v, pubsubConfigsProp)) { + log.Printf("[DEBUG] Calling update after create to patch in pubsub_configs") + // pubsub_configs cannot be added on create + return resourceSourceRepoRepositoryUpdate(d, meta) +} + +log.Printf("[DEBUG] Finished creating Repository %q: %#v", d.Id(), res) + +return resourceSourceRepoRepositoryRead(d, meta) diff --git a/mmv1/templates/terraform/extra_schema_entry/source_repo_repository.tmpl b/mmv1/templates/terraform/extra_schema_entry/source_repo_repository.tmpl new file mode 100644 index 000000000000..cca43823d7a8 --- /dev/null +++ b/mmv1/templates/terraform/extra_schema_entry/source_repo_repository.tmpl @@ -0,0 +1,6 @@ +"create_ignore_already_exists": { + Type: schema.TypeBool, + Optional: true, + Computed: false, + Description: `If set to true, skip repository creation if a repository with the same name already exists.`, +}, diff --git a/mmv1/third_party/terraform/services/sourcerepo/resource_sourcerepo_repository_test.go b/mmv1/third_party/terraform/services/sourcerepo/resource_sourcerepo_repository_test.go index b269bde99181..fa9815e16006 100644 --- a/mmv1/third_party/terraform/services/sourcerepo/resource_sourcerepo_repository_test.go +++ b/mmv1/third_party/terraform/services/sourcerepo/resource_sourcerepo_repository_test.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-provider-google/google/acctest" + "github.com/hashicorp/terraform-provider-google/google/envvar" ) func TestAccSourceRepoRepository_basic(t *testing.T) { @@ -89,3 +90,95 @@ func testAccSourceRepoRepository_extended(accountId string, topicName string, re } `, accountId, topicName, repositoryName) } + +// Test setting create_ignore_already_exists on an existing resource +func TestAccSourceRepoRepository_existingResourceCreateIgnoreAlreadyExists(t *testing.T) { + t.Parallel() + + project := envvar.GetTestProjectFromEnv() + repositoryName := fmt.Sprintf("source-repo-repository-test-%s", acctest.RandString(t, 10)) + id := fmt.Sprintf("projects/%s/repos/%s", project, repositoryName) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckSourceRepoRepositoryDestroyProducer(t), + Steps: []resource.TestStep{ + // The first step creates a new resource with create_ignore_already_exists=false + { + Config: testAccSourceRepoRepositoryCreateIgnoreAlreadyExists(repositoryName, false), + Check: resource.TestCheckResourceAttr("google_sourcerepo_repository.acceptance", "id", id), + }, + { + ResourceName: "google_sourcerepo_repository.acceptance", + ImportStateId: id, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"create_ignore_already_exists"}, // Import leaves this field out when false + }, + // The second step updates the resource to have create_ignore_already_exists=true + { + Config: testAccSourceRepoRepositoryCreateIgnoreAlreadyExists(repositoryName, true), + Check: resource.TestCheckResourceAttr("google_sourcerepo_repository.acceptance", "id", id), + }, + }, + }) +} + +// Test the option to ignore ALREADY_EXISTS error from creating a Source Repository. +func TestAccSourceRepoRepository_createIgnoreAlreadyExists(t *testing.T) { + t.Parallel() + + project := envvar.GetTestProjectFromEnv() + repositoryName := fmt.Sprintf("source-repo-repository-test-%s", acctest.RandString(t, 10)) + id := fmt.Sprintf("projects/%s/repos/%s", project, repositoryName) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckSourceRepoRepositoryDestroyProducer(t), + Steps: []resource.TestStep{ + // The first step creates a basic Source Repository + { + Config: testAccSourceRepoRepository_basic(repositoryName), + Check: resource.TestCheckResourceAttr("google_sourcerepo_repository.acceptance", "id", id), + }, + { + ResourceName: "google_sourcerepo_repository.acceptance", + ImportStateId: id, + ImportState: true, + ImportStateVerify: true, + }, + // The second step creates a new resource that duplicates with the existing Source Repository. + { + Config: testAccSourceRepoRepositoryDuplicateIgnoreAlreadyExists(repositoryName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_sourcerepo_repository.acceptance", "id", id), + resource.TestCheckResourceAttr("google_sourcerepo_repository.duplicate", "id", id), + ), + }, + }, + }) +} + +func testAccSourceRepoRepositoryCreateIgnoreAlreadyExists(repositoryName string, ignore_already_exists bool) string { + return fmt.Sprintf(` +resource "google_sourcerepo_repository" "acceptance" { + name = "%s" + create_ignore_already_exists = %t +} +`, repositoryName, ignore_already_exists) +} + +func testAccSourceRepoRepositoryDuplicateIgnoreAlreadyExists(repositoryName string) string { + return fmt.Sprintf(` +resource "google_sourcerepo_repository" "acceptance" { + name = "%s" +} + +resource "google_sourcerepo_repository" "duplicate" { + name = "%s" + create_ignore_already_exists = true +} +`, repositoryName, repositoryName) +}