Skip to content

Commit

Permalink
feat: automatic branch creation for resource 'github_repository_file' (
Browse files Browse the repository at this point in the history
…#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]>
  • Loading branch information
3 people authored Aug 6, 2024
1 parent 182f8e0 commit e6660d4
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 7 deletions.
105 changes: 99 additions & 6 deletions github/resource_github_repository_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,26 @@ func resourceGithubRepositoryFile() *schema.Resource {
Description: "Enable overwriting existing files, defaults to \"false\"",
Default: false,
},
"autocreate_branch": {
Type: schema.TypeBool,
Optional: true,
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'",
Default: false,
},
"autocreate_branch_source_branch": {
Type: schema.TypeString,
Default: "main",
Optional: true,
Description: "The branch name to start from, if 'autocreate_branch' is set. Defaults to 'main'.",
RequiredWith: []string{"autocreate_branch"},
},
"autocreate_branch_source_sha": {
Type: schema.TypeString,
Optional: true,
Computed: true,
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.",
RequiredWith: []string{"autocreate_branch"},
},
},
}
}
Expand Down Expand Up @@ -177,7 +197,29 @@ func resourceGithubRepositoryFileCreate(d *schema.ResourceData, meta interface{}
if branch, ok := d.GetOk("branch"); ok {
log.Printf("[DEBUG] Using explicitly set branch: %s", branch.(string))
if err := checkRepositoryBranchExists(client, owner, repo, branch.(string)); err != nil {
return err
if d.Get("autocreate_branch").(bool) {
branchRefName := "refs/heads/" + branch.(string)
sourceBranchName := d.Get("autocreate_branch_source_branch").(string)
sourceBranchRefName := "refs/heads/" + sourceBranchName

if _, hasSourceSHA := d.GetOk("autocreate_branch_source_sha"); !hasSourceSHA {
ref, _, err := client.Git.GetRef(ctx, owner, repo, sourceBranchRefName)
if err != nil {
return fmt.Errorf("error querying GitHub branch reference %s/%s (%s): %s",
owner, repo, sourceBranchRefName, err)
}
d.Set("autocreate_branch_source_sha", *ref.Object.SHA)
}
sourceBranchSHA := d.Get("autocreate_branch_source_sha").(string)
if _, _, err := client.Git.CreateRef(ctx, owner, repo, &github.Reference{
Ref: &branchRefName,
Object: &github.GitObject{SHA: &sourceBranchSHA},
}); err != nil {
return err
}
} else {
return err
}
}
checkOpt.Ref = branch.(string)
}
Expand Down Expand Up @@ -244,10 +286,14 @@ func resourceGithubRepositoryFileRead(d *schema.ResourceData, meta interface{})
if branch, ok := d.GetOk("branch"); ok {
log.Printf("[DEBUG] Using explicitly set branch: %s", branch.(string))
if err := checkRepositoryBranchExists(client, owner, repo, branch.(string)); err != nil {
log.Printf("[INFO] Removing repository path %s/%s/%s from state because the branch no longer exists in GitHub",
owner, repo, file)
d.SetId("")
return nil
if d.Get("autocreate_branch").(bool) {
branch = d.Get("autocreate_branch_source_branch").(string)
} else {
log.Printf("[INFO] Removing repository path %s/%s/%s from state because the branch no longer exists in GitHub",
owner, repo, file)
d.SetId("")
return nil
}
}
opts.Ref = branch.(string)
}
Expand Down Expand Up @@ -344,7 +390,29 @@ func resourceGithubRepositoryFileUpdate(d *schema.ResourceData, meta interface{}
if branch, ok := d.GetOk("branch"); ok {
log.Printf("[DEBUG] Using explicitly set branch: %s", branch.(string))
if err := checkRepositoryBranchExists(client, owner, repo, branch.(string)); err != nil {
return err
if d.Get("autocreate_branch").(bool) {
branchRefName := "refs/heads/" + branch.(string)
sourceBranchName := d.Get("autocreate_branch_source_branch").(string)
sourceBranchRefName := "refs/heads/" + sourceBranchName

if _, hasSourceSHA := d.GetOk("autocreate_branch_source_sha"); !hasSourceSHA {
ref, _, err := client.Git.GetRef(ctx, owner, repo, sourceBranchRefName)
if err != nil {
return fmt.Errorf("error querying GitHub branch reference %s/%s (%s): %s",
owner, repo, sourceBranchRefName, err)
}
d.Set("autocreate_branch_source_sha", *ref.Object.SHA)
}
sourceBranchSHA := d.Get("autocreate_branch_source_sha").(string)
if _, _, err := client.Git.CreateRef(ctx, owner, repo, &github.Reference{
Ref: &branchRefName,
Object: &github.GitObject{SHA: &sourceBranchSHA},
}); err != nil {
return err
}
} else {
return err
}
}
}

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

if b, ok := d.GetOk("branch"); ok {
log.Printf("[DEBUG] Using explicitly set branch: %s", b.(string))
if err := checkRepositoryBranchExists(client, owner, repo, b.(string)); err != nil {
if d.Get("autocreate_branch").(bool) {
branchRefName := "refs/heads/" + b.(string)
sourceBranchName := d.Get("autocreate_branch_source_branch").(string)
sourceBranchRefName := "refs/heads/" + sourceBranchName

if _, hasSourceSHA := d.GetOk("autocreate_branch_source_sha"); !hasSourceSHA {
ref, _, err := client.Git.GetRef(ctx, owner, repo, sourceBranchRefName)
if err != nil {
return fmt.Errorf("error querying GitHub branch reference %s/%s (%s): %s",
owner, repo, sourceBranchRefName, err)
}
d.Set("autocreate_branch_source_sha", *ref.Object.SHA)
}
sourceBranchSHA := d.Get("autocreate_branch_source_sha").(string)
if _, _, err := client.Git.CreateRef(ctx, owner, repo, &github.Reference{
Ref: &branchRefName,
Object: &github.GitObject{SHA: &sourceBranchSHA},
}); err != nil {
return err
}
} else {
return err
}
}
branch = b.(string)
opts.Branch = &branch
}
Expand Down
80 changes: 80 additions & 0 deletions github/resource_github_repository_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,4 +246,84 @@ func TestAccGithubRepositoryFile(t *testing.T) {
})

})

t.Run("creates and manages files on auto created branch if branch does not exist", func(t *testing.T) {

config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
auto_init = true
}
resource "github_repository_file" "test" {
repository = github_repository.test.name
branch = "does/not/exist"
file = "test"
content = "bar"
commit_message = "Managed by Terraform"
commit_author = "Terraform User"
commit_email = "[email protected]"
autocreate_branch = false
}
`, randomID)

check := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"github_repository_file.test", "content",
"bar",
),
resource.TestCheckResourceAttr(
"github_repository_file.test", "sha",
"ba0e162e1c47469e3fe4b393a8bf8c569f302116",
),
resource.TestCheckResourceAttr(
"github_repository_file.test", "ref",
"does/not/exist",
),
resource.TestCheckResourceAttrSet(
"github_repository_file.test", "commit_author",
),
resource.TestCheckResourceAttrSet(
"github_repository_file.test", "commit_email",
),
resource.TestCheckResourceAttrSet(
"github_repository_file.test", "commit_message",
),
resource.TestCheckResourceAttrSet(
"github_repository_file.test", "commit_sha",
),
)

testCase := func(t *testing.T, mode string) {
resource.Test(t, resource.TestCase{
PreCheck: func() { skipUnlessMode(t, mode) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: config,
ExpectError: regexp.MustCompile(`unexpected status code: 404 Not Found`),
},
{
Config: strings.Replace(config,
"autocreate_branch = false",
"autocreate_branch = true", 1),
Check: check,
},
},
})
}

t.Run("with an anonymous account", func(t *testing.T) {
t.Skip("anonymous account not supported for this operation")
})

t.Run("with an individual account", func(t *testing.T) {
testCase(t, individual)
})

t.Run("with an organization account", func(t *testing.T) {
testCase(t, organization)
})

})
}
31 changes: 30 additions & 1 deletion website/docs/r/repository_file.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ GitHub repository.

## Example Usage

### Existing Branch
```hcl
resource "github_repository" "foo" {
Expand All @@ -33,6 +34,28 @@ resource "github_repository_file" "foo" {
```

### Auto Created Branch
```hcl
resource "github_repository" "foo" {
name = "tf-acc-test-%s"
auto_init = true
}
resource "github_repository_file" "foo" {
repository = github_repository.foo.name
branch = "does/not/exist"
file = ".gitignore"
content = "**/*.tfstate"
commit_message = "Managed by Terraform"
commit_author = "Terraform User"
commit_email = "[email protected]"
overwrite_on_create = true
autocreate_branch = true
}
```


## Argument Reference

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

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

* `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.

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

* `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.

* `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'.

* `autocreate_branch_source_branch` - (Optional) The branch name to start from, if 'autocreate_branch' is set. Defaults to 'main'.

* `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.

## Attributes Reference

The following additional attributes are exported:
Expand Down

0 comments on commit e6660d4

Please sign in to comment.