Skip to content

Commit f911f12

Browse files
committed
feat: support automatic branch creation for resource 'github_repository_file'
1 parent 5752b25 commit f911f12

3 files changed

+207
-7
lines changed

github/resource_github_repository_file.go

+97-6
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,24 @@ func resourceGithubRepositoryFile() *schema.Resource {
115115
Description: "Enable overwriting existing files, defaults to \"false\"",
116116
Default: false,
117117
},
118+
"autocreate_branch": {
119+
Type: schema.TypeBool,
120+
Optional: true,
121+
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'",
122+
Default: false,
123+
},
124+
"autocreate_branch_source_branch": {
125+
Type: schema.TypeString,
126+
Default: "main",
127+
Optional: true,
128+
Description: "The branch name to start from, if 'autocreate_branch' is set. Defaults to 'main'.",
129+
},
130+
"autocreate_branch_source_sha": {
131+
Type: schema.TypeString,
132+
Optional: true,
133+
Computed: true,
134+
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.",
135+
},
118136
},
119137
}
120138
}
@@ -173,7 +191,29 @@ func resourceGithubRepositoryFileCreate(d *schema.ResourceData, meta interface{}
173191
if branch, ok := d.GetOk("branch"); ok {
174192
log.Printf("[DEBUG] Using explicitly set branch: %s", branch.(string))
175193
if err := checkRepositoryBranchExists(client, owner, repo, branch.(string)); err != nil {
176-
return err
194+
if d.Get("autocreate_branch").(bool) {
195+
branchRefName := "refs/heads/" + branch.(string)
196+
sourceBranchName := d.Get("autocreate_branch_source_branch").(string)
197+
sourceBranchRefName := "refs/heads/" + sourceBranchName
198+
199+
if _, hasSourceSHA := d.GetOk("autocreate_branch_source_sha"); !hasSourceSHA {
200+
ref, _, err := client.Git.GetRef(ctx, owner, repo, sourceBranchRefName)
201+
if err != nil {
202+
return fmt.Errorf("error querying GitHub branch reference %s/%s (%s): %s",
203+
owner, repo, sourceBranchRefName, err)
204+
}
205+
d.Set("autocreate_branch_source_sha", *ref.Object.SHA)
206+
}
207+
sourceBranchSHA := d.Get("autocreate_branch_source_sha").(string)
208+
if _, _, err := client.Git.CreateRef(ctx, owner, repo, &github.Reference{
209+
Ref: &branchRefName,
210+
Object: &github.GitObject{SHA: &sourceBranchSHA},
211+
}); err != nil {
212+
return err
213+
}
214+
} else {
215+
return err
216+
}
177217
}
178218
checkOpt.Ref = branch.(string)
179219
}
@@ -238,10 +278,14 @@ func resourceGithubRepositoryFileRead(d *schema.ResourceData, meta interface{})
238278
if branch, ok := d.GetOk("branch"); ok {
239279
log.Printf("[DEBUG] Using explicitly set branch: %s", branch.(string))
240280
if err := checkRepositoryBranchExists(client, owner, repo, branch.(string)); err != nil {
241-
log.Printf("[INFO] Removing repository path %s/%s/%s from state because the branch no longer exists in GitHub",
242-
owner, repo, file)
243-
d.SetId("")
244-
return nil
281+
if d.Get("autocreate_branch").(bool) {
282+
branch = d.Get("autocreate_branch_source_branch").(string)
283+
} else {
284+
log.Printf("[INFO] Removing repository path %s/%s/%s from state because the branch no longer exists in GitHub",
285+
owner, repo, file)
286+
d.SetId("")
287+
return nil
288+
}
245289
}
246290
opts.Ref = branch.(string)
247291
}
@@ -320,7 +364,29 @@ func resourceGithubRepositoryFileUpdate(d *schema.ResourceData, meta interface{}
320364
if branch, ok := d.GetOk("branch"); ok {
321365
log.Printf("[DEBUG] Using explicitly set branch: %s", branch.(string))
322366
if err := checkRepositoryBranchExists(client, owner, repo, branch.(string)); err != nil {
323-
return err
367+
if d.Get("autocreate_branch").(bool) {
368+
branchRefName := "refs/heads/" + branch.(string)
369+
sourceBranchName := d.Get("autocreate_branch_source_branch").(string)
370+
sourceBranchRefName := "refs/heads/" + sourceBranchName
371+
372+
if _, hasSourceSHA := d.GetOk("autocreate_branch_source_sha"); !hasSourceSHA {
373+
ref, _, err := client.Git.GetRef(ctx, owner, repo, sourceBranchRefName)
374+
if err != nil {
375+
return fmt.Errorf("error querying GitHub branch reference %s/%s (%s): %s",
376+
owner, repo, sourceBranchRefName, err)
377+
}
378+
d.Set("autocreate_branch_source_sha", *ref.Object.SHA)
379+
}
380+
sourceBranchSHA := d.Get("autocreate_branch_source_sha").(string)
381+
if _, _, err := client.Git.CreateRef(ctx, owner, repo, &github.Reference{
382+
Ref: &branchRefName,
383+
Object: &github.GitObject{SHA: &sourceBranchSHA},
384+
}); err != nil {
385+
return err
386+
}
387+
} else {
388+
return err
389+
}
324390
}
325391
}
326392

@@ -369,6 +435,31 @@ func resourceGithubRepositoryFileDelete(d *schema.ResourceData, meta interface{}
369435

370436
if b, ok := d.GetOk("branch"); ok {
371437
log.Printf("[DEBUG] Using explicitly set branch: %s", b.(string))
438+
if err := checkRepositoryBranchExists(client, owner, repo, b.(string)); err != nil {
439+
if d.Get("autocreate_branch").(bool) {
440+
branchRefName := "refs/heads/" + b.(string)
441+
sourceBranchName := d.Get("autocreate_branch_source_branch").(string)
442+
sourceBranchRefName := "refs/heads/" + sourceBranchName
443+
444+
if _, hasSourceSHA := d.GetOk("autocreate_branch_source_sha"); !hasSourceSHA {
445+
ref, _, err := client.Git.GetRef(ctx, owner, repo, sourceBranchRefName)
446+
if err != nil {
447+
return fmt.Errorf("error querying GitHub branch reference %s/%s (%s): %s",
448+
owner, repo, sourceBranchRefName, err)
449+
}
450+
d.Set("autocreate_branch_source_sha", *ref.Object.SHA)
451+
}
452+
sourceBranchSHA := d.Get("autocreate_branch_source_sha").(string)
453+
if _, _, err := client.Git.CreateRef(ctx, owner, repo, &github.Reference{
454+
Ref: &branchRefName,
455+
Object: &github.GitObject{SHA: &sourceBranchSHA},
456+
}); err != nil {
457+
return err
458+
}
459+
} else {
460+
return err
461+
}
462+
}
372463
branch = b.(string)
373464
opts.Branch = &branch
374465
}

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
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)