Skip to content

Commit e6660d4

Browse files
wparr-circlekfcampbellnickfloyd
authored
feat: automatic branch creation for resource 'github_repository_file' (#2100)
* feat: support automatic branch creation for resource 'github_repository_file' * fix: add RequiredWith to new optional variables --------- Co-authored-by: Keegan Campbell <[email protected]> Co-authored-by: Nick Floyd <[email protected]>
1 parent 182f8e0 commit e6660d4

3 files changed

+209
-7
lines changed

github/resource_github_repository_file.go

+99-6
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,26 @@ func resourceGithubRepositoryFile() *schema.Resource {
119119
Description: "Enable overwriting existing files, defaults to \"false\"",
120120
Default: false,
121121
},
122+
"autocreate_branch": {
123+
Type: schema.TypeBool,
124+
Optional: true,
125+
Description: "Automatically create the branch if it could not be found. Subsequent reads if the branch is deleted will occur from 'autocreate_branch_source_branch'",
126+
Default: false,
127+
},
128+
"autocreate_branch_source_branch": {
129+
Type: schema.TypeString,
130+
Default: "main",
131+
Optional: true,
132+
Description: "The branch name to start from, if 'autocreate_branch' is set. Defaults to 'main'.",
133+
RequiredWith: []string{"autocreate_branch"},
134+
},
135+
"autocreate_branch_source_sha": {
136+
Type: schema.TypeString,
137+
Optional: true,
138+
Computed: true,
139+
Description: "The commit hash to start from, if 'autocreate_branch' is set. Defaults to the tip of 'autocreate_branch_source_branch'. If provided, 'autocreate_branch_source_branch' is ignored.",
140+
RequiredWith: []string{"autocreate_branch"},
141+
},
122142
},
123143
}
124144
}
@@ -177,7 +197,29 @@ func resourceGithubRepositoryFileCreate(d *schema.ResourceData, meta interface{}
177197
if branch, ok := d.GetOk("branch"); ok {
178198
log.Printf("[DEBUG] Using explicitly set branch: %s", branch.(string))
179199
if err := checkRepositoryBranchExists(client, owner, repo, branch.(string)); err != nil {
180-
return err
200+
if d.Get("autocreate_branch").(bool) {
201+
branchRefName := "refs/heads/" + branch.(string)
202+
sourceBranchName := d.Get("autocreate_branch_source_branch").(string)
203+
sourceBranchRefName := "refs/heads/" + sourceBranchName
204+
205+
if _, hasSourceSHA := d.GetOk("autocreate_branch_source_sha"); !hasSourceSHA {
206+
ref, _, err := client.Git.GetRef(ctx, owner, repo, sourceBranchRefName)
207+
if err != nil {
208+
return fmt.Errorf("error querying GitHub branch reference %s/%s (%s): %s",
209+
owner, repo, sourceBranchRefName, err)
210+
}
211+
d.Set("autocreate_branch_source_sha", *ref.Object.SHA)
212+
}
213+
sourceBranchSHA := d.Get("autocreate_branch_source_sha").(string)
214+
if _, _, err := client.Git.CreateRef(ctx, owner, repo, &github.Reference{
215+
Ref: &branchRefName,
216+
Object: &github.GitObject{SHA: &sourceBranchSHA},
217+
}); err != nil {
218+
return err
219+
}
220+
} else {
221+
return err
222+
}
181223
}
182224
checkOpt.Ref = branch.(string)
183225
}
@@ -244,10 +286,14 @@ func resourceGithubRepositoryFileRead(d *schema.ResourceData, meta interface{})
244286
if branch, ok := d.GetOk("branch"); ok {
245287
log.Printf("[DEBUG] Using explicitly set branch: %s", branch.(string))
246288
if err := checkRepositoryBranchExists(client, owner, repo, branch.(string)); err != nil {
247-
log.Printf("[INFO] Removing repository path %s/%s/%s from state because the branch no longer exists in GitHub",
248-
owner, repo, file)
249-
d.SetId("")
250-
return nil
289+
if d.Get("autocreate_branch").(bool) {
290+
branch = d.Get("autocreate_branch_source_branch").(string)
291+
} else {
292+
log.Printf("[INFO] Removing repository path %s/%s/%s from state because the branch no longer exists in GitHub",
293+
owner, repo, file)
294+
d.SetId("")
295+
return nil
296+
}
251297
}
252298
opts.Ref = branch.(string)
253299
}
@@ -344,7 +390,29 @@ func resourceGithubRepositoryFileUpdate(d *schema.ResourceData, meta interface{}
344390
if branch, ok := d.GetOk("branch"); ok {
345391
log.Printf("[DEBUG] Using explicitly set branch: %s", branch.(string))
346392
if err := checkRepositoryBranchExists(client, owner, repo, branch.(string)); err != nil {
347-
return err
393+
if d.Get("autocreate_branch").(bool) {
394+
branchRefName := "refs/heads/" + branch.(string)
395+
sourceBranchName := d.Get("autocreate_branch_source_branch").(string)
396+
sourceBranchRefName := "refs/heads/" + sourceBranchName
397+
398+
if _, hasSourceSHA := d.GetOk("autocreate_branch_source_sha"); !hasSourceSHA {
399+
ref, _, err := client.Git.GetRef(ctx, owner, repo, sourceBranchRefName)
400+
if err != nil {
401+
return fmt.Errorf("error querying GitHub branch reference %s/%s (%s): %s",
402+
owner, repo, sourceBranchRefName, err)
403+
}
404+
d.Set("autocreate_branch_source_sha", *ref.Object.SHA)
405+
}
406+
sourceBranchSHA := d.Get("autocreate_branch_source_sha").(string)
407+
if _, _, err := client.Git.CreateRef(ctx, owner, repo, &github.Reference{
408+
Ref: &branchRefName,
409+
Object: &github.GitObject{SHA: &sourceBranchSHA},
410+
}); err != nil {
411+
return err
412+
}
413+
} else {
414+
return err
415+
}
348416
}
349417
}
350418

@@ -395,6 +463,31 @@ func resourceGithubRepositoryFileDelete(d *schema.ResourceData, meta interface{}
395463

396464
if b, ok := d.GetOk("branch"); ok {
397465
log.Printf("[DEBUG] Using explicitly set branch: %s", b.(string))
466+
if err := checkRepositoryBranchExists(client, owner, repo, b.(string)); err != nil {
467+
if d.Get("autocreate_branch").(bool) {
468+
branchRefName := "refs/heads/" + b.(string)
469+
sourceBranchName := d.Get("autocreate_branch_source_branch").(string)
470+
sourceBranchRefName := "refs/heads/" + sourceBranchName
471+
472+
if _, hasSourceSHA := d.GetOk("autocreate_branch_source_sha"); !hasSourceSHA {
473+
ref, _, err := client.Git.GetRef(ctx, owner, repo, sourceBranchRefName)
474+
if err != nil {
475+
return fmt.Errorf("error querying GitHub branch reference %s/%s (%s): %s",
476+
owner, repo, sourceBranchRefName, err)
477+
}
478+
d.Set("autocreate_branch_source_sha", *ref.Object.SHA)
479+
}
480+
sourceBranchSHA := d.Get("autocreate_branch_source_sha").(string)
481+
if _, _, err := client.Git.CreateRef(ctx, owner, repo, &github.Reference{
482+
Ref: &branchRefName,
483+
Object: &github.GitObject{SHA: &sourceBranchSHA},
484+
}); err != nil {
485+
return err
486+
}
487+
} else {
488+
return err
489+
}
490+
}
398491
branch = b.(string)
399492
opts.Branch = &branch
400493
}

github/resource_github_repository_file_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -246,4 +246,84 @@ func TestAccGithubRepositoryFile(t *testing.T) {
246246
})
247247

248248
})
249+
250+
t.Run("creates and manages files on auto created branch if branch does not exist", func(t *testing.T) {
251+
252+
config := fmt.Sprintf(`
253+
resource "github_repository" "test" {
254+
name = "tf-acc-test-%s"
255+
auto_init = true
256+
}
257+
258+
resource "github_repository_file" "test" {
259+
repository = github_repository.test.name
260+
branch = "does/not/exist"
261+
file = "test"
262+
content = "bar"
263+
commit_message = "Managed by Terraform"
264+
commit_author = "Terraform User"
265+
commit_email = "[email protected]"
266+
autocreate_branch = false
267+
}
268+
`, randomID)
269+
270+
check := resource.ComposeTestCheckFunc(
271+
resource.TestCheckResourceAttr(
272+
"github_repository_file.test", "content",
273+
"bar",
274+
),
275+
resource.TestCheckResourceAttr(
276+
"github_repository_file.test", "sha",
277+
"ba0e162e1c47469e3fe4b393a8bf8c569f302116",
278+
),
279+
resource.TestCheckResourceAttr(
280+
"github_repository_file.test", "ref",
281+
"does/not/exist",
282+
),
283+
resource.TestCheckResourceAttrSet(
284+
"github_repository_file.test", "commit_author",
285+
),
286+
resource.TestCheckResourceAttrSet(
287+
"github_repository_file.test", "commit_email",
288+
),
289+
resource.TestCheckResourceAttrSet(
290+
"github_repository_file.test", "commit_message",
291+
),
292+
resource.TestCheckResourceAttrSet(
293+
"github_repository_file.test", "commit_sha",
294+
),
295+
)
296+
297+
testCase := func(t *testing.T, mode string) {
298+
resource.Test(t, resource.TestCase{
299+
PreCheck: func() { skipUnlessMode(t, mode) },
300+
Providers: testAccProviders,
301+
Steps: []resource.TestStep{
302+
{
303+
Config: config,
304+
ExpectError: regexp.MustCompile(`unexpected status code: 404 Not Found`),
305+
},
306+
{
307+
Config: strings.Replace(config,
308+
"autocreate_branch = false",
309+
"autocreate_branch = true", 1),
310+
Check: check,
311+
},
312+
},
313+
})
314+
}
315+
316+
t.Run("with an anonymous account", func(t *testing.T) {
317+
t.Skip("anonymous account not supported for this operation")
318+
})
319+
320+
t.Run("with an individual account", func(t *testing.T) {
321+
testCase(t, individual)
322+
})
323+
324+
t.Run("with an organization account", func(t *testing.T) {
325+
testCase(t, organization)
326+
})
327+
328+
})
249329
}

website/docs/r/repository_file.html.markdown

+30-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ GitHub repository.
1313

1414
## Example Usage
1515

16+
### Existing Branch
1617
```hcl
1718
1819
resource "github_repository" "foo" {
@@ -33,6 +34,28 @@ resource "github_repository_file" "foo" {
3334
3435
```
3536

37+
### Auto Created Branch
38+
```hcl
39+
40+
resource "github_repository" "foo" {
41+
name = "tf-acc-test-%s"
42+
auto_init = true
43+
}
44+
45+
resource "github_repository_file" "foo" {
46+
repository = github_repository.foo.name
47+
branch = "does/not/exist"
48+
file = ".gitignore"
49+
content = "**/*.tfstate"
50+
commit_message = "Managed by Terraform"
51+
commit_author = "Terraform User"
52+
commit_email = "[email protected]"
53+
overwrite_on_create = true
54+
autocreate_branch = true
55+
}
56+
57+
```
58+
3659

3760
## Argument Reference
3861

@@ -45,7 +68,7 @@ The following arguments are supported:
4568
* `content` - (Required) The file content.
4669

4770
* `branch` - (Optional) Git branch (defaults to the repository's default branch).
48-
The branch must already exist, it will not be created if it does not already exist.
71+
The branch must already exist, it will only be created automatically if 'autocreate_branch' is set true.
4972

5073
* `commit_author` - (Optional) Committer author name to use. **NOTE:** GitHub app users may omit author and email information so GitHub can verify commits as the GitHub App. This maybe useful when a branch protection rule requires signed commits.
5174

@@ -55,6 +78,12 @@ The following arguments are supported:
5578

5679
* `overwrite_on_create` - (Optional) Enable overwriting existing files. If set to `true` it will overwrite an existing file with the same name. If set to `false` it will fail if there is an existing file with the same name.
5780

81+
* `autocreate_branch` - (Optional) Automatically create the branch if it could not be found. Defaults to false. Subsequent reads if the branch is deleted will occur from 'autocreate_branch_source_branch'.
82+
83+
* `autocreate_branch_source_branch` - (Optional) The branch name to start from, if 'autocreate_branch' is set. Defaults to 'main'.
84+
85+
* `autocreate_branch_source_sha` - (Optional) The commit hash to start from, if 'autocreate_branch' is set. Defaults to the tip of 'autocreate_branch_source_branch'. If provided, 'autocreate_branch_source_branch' is ignored.
86+
5887
## Attributes Reference
5988

6089
The following additional attributes are exported:

0 commit comments

Comments
 (0)