Skip to content

Commit 5ea0846

Browse files
daniel-citzli82016
authored andcommitted
Add create_ignore_already_exists to google_sourcerepo_repository (GoogleCloudPlatform#11770)
Co-authored-by: Zhenhua Li <[email protected]>
1 parent 358544b commit 5ea0846

File tree

5 files changed

+247
-0
lines changed

5 files changed

+247
-0
lines changed

mmv1/products/sourcerepo/Repository.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ references:
2020
'Official Documentation': 'https://cloud.google.com/source-repositories/'
2121
api: 'https://cloud.google.com/source-repositories/docs/reference/rest/v1/projects.repos'
2222
docs:
23+
optional_properties: |
24+
* `create_ignore_already_exists` - (Optional) If set to true, skip repository creation if a repository with the same name already exists.
2325
base_url: 'projects/{{project}}/repos'
2426
self_link: 'projects/{{project}}/repos/{{name}}'
2527
update_verb: 'PATCH'
@@ -41,6 +43,8 @@ custom_code:
4143
constants: 'templates/terraform/constants/source_repo_repository.go.tmpl'
4244
update_encoder: 'templates/terraform/update_encoder/source_repo_repository.tmpl'
4345
post_create: 'templates/terraform/post_create/source_repo_repository_update.go.tmpl'
46+
extra_schema_entry: templates/terraform/extra_schema_entry/source_repo_repository.tmpl
47+
custom_create: templates/terraform/custom_create/source_repo_repository.go.tmpl
4448
exclude_tgc: true
4549
examples:
4650
- name: 'sourcerepo_repository_basic'

mmv1/templates/terraform/constants/source_repo_repository.go.tmpl

+44
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,47 @@ func resourceSourceRepoRepositoryPubSubConfigsHash(v interface{}) int {
2626

2727
return tpgresource.Hashcode(buf.String())
2828
}
29+
30+
func resourceSourceRepoRepositoryPollRead(d *schema.ResourceData, meta interface{}) transport_tpg.PollReadFunc {
31+
return func() (map[string]interface{}, error) {
32+
config := meta.(*transport_tpg.Config)
33+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
34+
if err != nil {
35+
return nil, err
36+
}
37+
38+
url, err := tpgresource.ReplaceVars(d, config, "{{"{{SourceRepoBasePath}}projects/{{project}}/repos"}}")
39+
if err != nil {
40+
return nil, err
41+
}
42+
43+
billingProject := ""
44+
45+
project, err := tpgresource.GetProject(d, config)
46+
if err != nil {
47+
return nil, fmt.Errorf("error fetching project for Repository: %s", err)
48+
}
49+
billingProject = project
50+
51+
// err == nil indicates that the billing_project value was found
52+
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
53+
billingProject = bp
54+
}
55+
56+
// Confirm the source repository exists
57+
headers := make(http.Header)
58+
_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
59+
Config: config,
60+
Method: "GET",
61+
Project: billingProject,
62+
RawURL: url,
63+
UserAgent: userAgent,
64+
Headers: headers,
65+
})
66+
67+
if err != nil {
68+
return nil, err
69+
}
70+
return nil, nil
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
2+
if err != nil {
3+
return err
4+
}
5+
6+
obj := make(map[string]interface{})
7+
nameProp, err := expandSourceRepoRepositoryName(d.Get("name"), d, config)
8+
if err != nil {
9+
return err
10+
} else if v, ok := d.GetOkExists("name"); !tpgresource.IsEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) {
11+
obj["name"] = nameProp
12+
}
13+
pubsubConfigsProp, err := expandSourceRepoRepositoryPubsubConfigs(d.Get("pubsub_configs"), d, config)
14+
if err != nil {
15+
return err
16+
} else if v, ok := d.GetOkExists("pubsub_configs"); !tpgresource.IsEmptyValue(reflect.ValueOf(pubsubConfigsProp)) && (ok || !reflect.DeepEqual(v, pubsubConfigsProp)) {
17+
obj["pubsubConfigs"] = pubsubConfigsProp
18+
}
19+
20+
url, err := tpgresource.ReplaceVars(d, config, "{{"{{SourceRepoBasePath}}projects/{{project}}/repos"}}")
21+
if err != nil {
22+
return err
23+
}
24+
25+
log.Printf("[DEBUG] Creating new Repository: %#v", obj)
26+
billingProject := ""
27+
28+
project, err := tpgresource.GetProject(d, config)
29+
if err != nil {
30+
return fmt.Errorf("Error fetching project for Repository: %s", err)
31+
}
32+
billingProject = project
33+
34+
// err == nil indicates that the billing_project value was found
35+
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
36+
billingProject = bp
37+
}
38+
39+
headers := make(http.Header)
40+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
41+
Config: config,
42+
Method: "POST",
43+
Project: billingProject,
44+
RawURL: url,
45+
UserAgent: userAgent,
46+
Body: obj,
47+
Timeout: d.Timeout(schema.TimeoutCreate),
48+
Headers: headers,
49+
})
50+
if err != nil {
51+
gerr, ok := err.(*googleapi.Error)
52+
alreadyExists := ok && gerr.Code == 409 && d.Get("create_ignore_already_exists").(bool)
53+
if alreadyExists {
54+
log.Printf("[DEBUG] Calling get Repository after already exists error")
55+
res, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
56+
Config: config,
57+
Method: "GET",
58+
Project: billingProject,
59+
RawURL: url,
60+
UserAgent: userAgent,
61+
Headers: headers,
62+
})
63+
if err != nil {
64+
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("SourceRepoRepository %q", d.Id()))
65+
}
66+
} else {
67+
return fmt.Errorf("Error creating Repository: %s", err)
68+
}
69+
}
70+
71+
72+
// We poll until the resource is found due to eventual consistency issue
73+
// on part of the api https://cloud.google.com/iam/docs/overview#consistency
74+
err = transport_tpg.PollingWaitTime(resourceSourceRepoRepositoryPollRead(d, meta), transport_tpg.PollCheckForExistence, "Creating Source Repository", d.Timeout(schema.TimeoutCreate), 1)
75+
76+
if err != nil {
77+
return err
78+
}
79+
80+
// We can't guarantee complete consistency even after polling,
81+
// so sleep for some additional time to reduce the likelihood of
82+
// eventual consistency failures.
83+
time.Sleep(10 * time.Second)
84+
85+
// Store the ID now
86+
id, err := tpgresource.ReplaceVars(d, config, "{{"projects/{{project}}/repos/{{name}}"}}")
87+
if err != nil {
88+
return fmt.Errorf("Error constructing id: %s", err)
89+
}
90+
d.SetId(id)
91+
92+
if v, ok := d.GetOkExists("pubsub_configs"); !tpgresource.IsEmptyValue(reflect.ValueOf(pubsubConfigsProp)) && (ok || !reflect.DeepEqual(v, pubsubConfigsProp)) {
93+
log.Printf("[DEBUG] Calling update after create to patch in pubsub_configs")
94+
// pubsub_configs cannot be added on create
95+
return resourceSourceRepoRepositoryUpdate(d, meta)
96+
}
97+
98+
log.Printf("[DEBUG] Finished creating Repository %q: %#v", d.Id(), res)
99+
100+
return resourceSourceRepoRepositoryRead(d, meta)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
"create_ignore_already_exists": {
2+
Type: schema.TypeBool,
3+
Optional: true,
4+
Computed: false,
5+
Description: `If set to true, skip repository creation if a repository with the same name already exists.`,
6+
},

mmv1/third_party/terraform/services/sourcerepo/resource_sourcerepo_repository_test.go

+93
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
88
"github.com/hashicorp/terraform-provider-google/google/acctest"
9+
"github.com/hashicorp/terraform-provider-google/google/envvar"
910
)
1011

1112
func TestAccSourceRepoRepository_basic(t *testing.T) {
@@ -89,3 +90,95 @@ func testAccSourceRepoRepository_extended(accountId string, topicName string, re
8990
}
9091
`, accountId, topicName, repositoryName)
9192
}
93+
94+
// Test setting create_ignore_already_exists on an existing resource
95+
func TestAccSourceRepoRepository_existingResourceCreateIgnoreAlreadyExists(t *testing.T) {
96+
t.Parallel()
97+
98+
project := envvar.GetTestProjectFromEnv()
99+
repositoryName := fmt.Sprintf("source-repo-repository-test-%s", acctest.RandString(t, 10))
100+
id := fmt.Sprintf("projects/%s/repos/%s", project, repositoryName)
101+
102+
acctest.VcrTest(t, resource.TestCase{
103+
PreCheck: func() { acctest.AccTestPreCheck(t) },
104+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
105+
CheckDestroy: testAccCheckSourceRepoRepositoryDestroyProducer(t),
106+
Steps: []resource.TestStep{
107+
// The first step creates a new resource with create_ignore_already_exists=false
108+
{
109+
Config: testAccSourceRepoRepositoryCreateIgnoreAlreadyExists(repositoryName, false),
110+
Check: resource.TestCheckResourceAttr("google_sourcerepo_repository.acceptance", "id", id),
111+
},
112+
{
113+
ResourceName: "google_sourcerepo_repository.acceptance",
114+
ImportStateId: id,
115+
ImportState: true,
116+
ImportStateVerify: true,
117+
ImportStateVerifyIgnore: []string{"create_ignore_already_exists"}, // Import leaves this field out when false
118+
},
119+
// The second step updates the resource to have create_ignore_already_exists=true
120+
{
121+
Config: testAccSourceRepoRepositoryCreateIgnoreAlreadyExists(repositoryName, true),
122+
Check: resource.TestCheckResourceAttr("google_sourcerepo_repository.acceptance", "id", id),
123+
},
124+
},
125+
})
126+
}
127+
128+
// Test the option to ignore ALREADY_EXISTS error from creating a Source Repository.
129+
func TestAccSourceRepoRepository_createIgnoreAlreadyExists(t *testing.T) {
130+
t.Parallel()
131+
132+
project := envvar.GetTestProjectFromEnv()
133+
repositoryName := fmt.Sprintf("source-repo-repository-test-%s", acctest.RandString(t, 10))
134+
id := fmt.Sprintf("projects/%s/repos/%s", project, repositoryName)
135+
136+
acctest.VcrTest(t, resource.TestCase{
137+
PreCheck: func() { acctest.AccTestPreCheck(t) },
138+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
139+
CheckDestroy: testAccCheckSourceRepoRepositoryDestroyProducer(t),
140+
Steps: []resource.TestStep{
141+
// The first step creates a basic Source Repository
142+
{
143+
Config: testAccSourceRepoRepository_basic(repositoryName),
144+
Check: resource.TestCheckResourceAttr("google_sourcerepo_repository.acceptance", "id", id),
145+
},
146+
{
147+
ResourceName: "google_sourcerepo_repository.acceptance",
148+
ImportStateId: id,
149+
ImportState: true,
150+
ImportStateVerify: true,
151+
},
152+
// The second step creates a new resource that duplicates with the existing Source Repository.
153+
{
154+
Config: testAccSourceRepoRepositoryDuplicateIgnoreAlreadyExists(repositoryName),
155+
Check: resource.ComposeTestCheckFunc(
156+
resource.TestCheckResourceAttr("google_sourcerepo_repository.acceptance", "id", id),
157+
resource.TestCheckResourceAttr("google_sourcerepo_repository.duplicate", "id", id),
158+
),
159+
},
160+
},
161+
})
162+
}
163+
164+
func testAccSourceRepoRepositoryCreateIgnoreAlreadyExists(repositoryName string, ignore_already_exists bool) string {
165+
return fmt.Sprintf(`
166+
resource "google_sourcerepo_repository" "acceptance" {
167+
name = "%s"
168+
create_ignore_already_exists = %t
169+
}
170+
`, repositoryName, ignore_already_exists)
171+
}
172+
173+
func testAccSourceRepoRepositoryDuplicateIgnoreAlreadyExists(repositoryName string) string {
174+
return fmt.Sprintf(`
175+
resource "google_sourcerepo_repository" "acceptance" {
176+
name = "%s"
177+
}
178+
179+
resource "google_sourcerepo_repository" "duplicate" {
180+
name = "%s"
181+
create_ignore_already_exists = true
182+
}
183+
`, repositoryName, repositoryName)
184+
}

0 commit comments

Comments
 (0)