Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions github/repository_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ import (
"strings"

"github.com/google/go-github/v82/github"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

// checkRepositoryBranchExists tests if a branch exists in a repository.
func checkRepositoryBranchExists(client *github.Client, owner, repo, branch string) error {
ctx := context.WithValue(context.Background(), ctxId, buildTwoPartID(repo, branch))
func checkRepositoryBranchExists(ctx context.Context, client *github.Client, owner, repo, branch string) error {
tflog.Debug(ctx, "Checking if branch exists", map[string]any{
"branch": branch,
"owner": owner,
"repo": repo,
})
_, _, err := client.Repositories.GetBranch(ctx, owner, repo, branch, 2)
if err != nil {
var ghErr *github.ErrorResponse
Expand Down
181 changes: 110 additions & 71 deletions github/resource_github_repository_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,28 @@ func resourceGithubRepositoryFile() *schema.Resource {
StateContext: resourceGithubRepositoryFileImport,
},

SchemaVersion: 1,
StateUpgraders: []schema.StateUpgrader{
{
Type: resourceGithubRepositoryFileV0().CoreConfigSchema().ImpliedType(),
Upgrade: resourceGithubRepositoryFileStateUpgradeV0,
Version: 0,
},
},

Description: "This resource allows you to create and manage files within a GitHub repository.",

Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The repository name",
},
"repository_id": {
Type: schema.TypeInt,
Computed: true,
Description: "The repository ID",
},
"file": {
Type: schema.TypeString,
Required: true,
Expand All @@ -47,6 +60,7 @@ func resourceGithubRepositoryFile() *schema.Resource {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Computed: true,
Description: "The branch name, defaults to the repository's default branch",
},
"ref": {
Expand Down Expand Up @@ -97,6 +111,7 @@ func resourceGithubRepositoryFile() *schema.Resource {
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,
DiffSuppressFunc: autoBranchDiffSuppressFunc,
Deprecated: "Use `github_branch` resource instead",
},
"autocreate_branch_source_branch": {
Type: schema.TypeString,
Expand All @@ -105,6 +120,7 @@ func resourceGithubRepositoryFile() *schema.Resource {
Description: "The branch name to start from, if 'autocreate_branch' is set. Defaults to 'main'.",
RequiredWith: []string{"autocreate_branch"},
DiffSuppressFunc: autoBranchDiffSuppressFunc,
Deprecated: "Use `github_branch` resource instead",
},
"autocreate_branch_source_sha": {
Type: schema.TypeString,
Expand All @@ -113,8 +129,10 @@ func resourceGithubRepositoryFile() *schema.Resource {
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"},
DiffSuppressFunc: autoBranchDiffSuppressFunc,
Deprecated: "Use `github_branch` resource instead",
},
},
CustomizeDiff: diffRepository,
}
}

Expand Down Expand Up @@ -161,11 +179,18 @@ func resourceGithubRepositoryFileCreate(ctx context.Context, d *schema.ResourceD

checkOpt := github.RepositoryContentGetOptions{}

if branch, ok := d.GetOk("branch"); ok {
repoInfo, _, err := client.Repositories.Get(ctx, owner, repo)
Comment thread
deiga marked this conversation as resolved.
if err != nil {
return diag.FromErr(err)
}
var branch string

if branchFieldVal, ok := d.GetOk("branch"); ok {
branch = branchFieldVal.(string)
tflog.Debug(ctx, "Using explicitly set branch", map[string]any{
"branch": branch.(string),
"branch": branch,
})
if err := checkRepositoryBranchExists(client, owner, repo, branch.(string)); err != nil {
if err := checkRepositoryBranchExists(ctx, client, owner, repo, branch); err != nil {
if d.Get("autocreate_branch").(bool) {
if err := resourceGithubRepositoryFileCreateBranch(ctx, d, meta); err != nil {
return diag.FromErr(err)
Expand All @@ -174,7 +199,9 @@ func resourceGithubRepositoryFileCreate(ctx context.Context, d *schema.ResourceD
return diag.FromErr(err)
}
}
checkOpt.Ref = branch.(string)
checkOpt.Ref = branch
Comment thread
deiga marked this conversation as resolved.
Outdated
} else {
branch = repoInfo.GetDefaultBranch()
}

opts := resourceGithubRepositoryFileOptions(d)
Expand Down Expand Up @@ -215,10 +242,25 @@ func resourceGithubRepositoryFileCreate(ctx context.Context, d *schema.ResourceD
return diag.FromErr(err)
}

d.SetId(fmt.Sprintf("%s/%s", repo, file))
newResourceID, err := buildID(repo, file, branch)
if err != nil {
return diag.FromErr(err)
}
tflog.Debug(ctx, "Setting ID", map[string]any{
"id": newResourceID,
})
d.SetId(newResourceID)

// Set computed values after the resource is created and in state
if err = d.Set("commit_sha", create.GetSHA()); err != nil {
return diag.FromErr(err)
}
if err := d.Set("branch", branch); err != nil {
return diag.FromErr(err)
}
if err := d.Set("repository_id", int(repoInfo.GetID())); err != nil {
return diag.FromErr(err)
}

return resourceGithubRepositoryFileRead(ctx, d, meta)
Comment thread
stevehipwell marked this conversation as resolved.
}
Expand All @@ -227,35 +269,26 @@ func resourceGithubRepositoryFileRead(ctx context.Context, d *schema.ResourceDat
client := meta.(*Owner).v3client
owner := meta.(*Owner).name

repo, file := splitRepoFilePath(d.Id())
ctx = tflog.SetField(ctx, "repository", repo)
repoName := d.Get("repository").(string)
file := d.Get("file").(string)
branch := d.Get("branch").(string)

ctx = tflog.SetField(ctx, "repository", repoName)
ctx = tflog.SetField(ctx, "file", file)
ctx = tflog.SetField(ctx, "owner", owner)
ctx = tflog.SetField(ctx, "owner", owner)

opts := &github.RepositoryContentGetOptions{}

if branch, ok := d.GetOk("branch"); ok {
tflog.Debug(ctx, "Using explicitly set branch", map[string]any{
"branch": branch.(string),
})
if err := checkRepositoryBranchExists(client, owner, repo, branch.(string)); err != nil {
if d.Get("autocreate_branch").(bool) {
branch = d.Get("autocreate_branch_source_branch").(string)
} else {
tflog.Info(ctx, "Removing repository path from state because the branch no longer exists in GitHub")
d.SetId("")
return nil
}
}
opts.Ref = branch.(string)
}
opts.Ref = branch

fc, _, _, err := client.Repositories.GetContents(ctx, owner, repo, file, opts)
fc, _, _, err := client.Repositories.GetContents(ctx, owner, repoName, file, opts)
if err != nil {
var errorResponse *github.ErrorResponse
if errors.As(err, &errorResponse) && errorResponse.Response.StatusCode == http.StatusTooManyRequests {
Comment thread
deiga marked this conversation as resolved.
Outdated
return diag.FromErr(err)
}
return diag.FromErr(deleteResourceOn404AndSwallow304OtherwiseReturnError(err, d, "repository file %s/%s:%s:%s", owner, repoName, file, branch))
Comment thread
stevehipwell marked this conversation as resolved.
}
if fc == nil {
tflog.Info(ctx, "Removing repository path from state because it no longer exists in GitHub")
Expand All @@ -271,12 +304,7 @@ func resourceGithubRepositoryFileRead(ctx context.Context, d *schema.ResourceDat
if err = d.Set("content", content); err != nil {
return diag.FromErr(err)
}
if err = d.Set("repository", repo); err != nil {
return diag.FromErr(err)
}
if err = d.Set("file", file); err != nil {
return diag.FromErr(err)
}

if err = d.Set("sha", fc.GetSHA()); err != nil {
return diag.FromErr(err)
}
Expand All @@ -301,10 +329,10 @@ func resourceGithubRepositoryFileRead(ctx context.Context, d *schema.ResourceDat
tflog.Debug(ctx, "Using known commit SHA", map[string]any{
"commit_sha": sha.(string),
})
commit, _, err = client.Repositories.GetCommit(ctx, owner, repo, sha.(string), nil)
commit, _, err = client.Repositories.GetCommit(ctx, owner, repoName, sha.(string), nil)
} else {
tflog.Debug(ctx, "Commit SHA unknown for file, looking for commit")
commit, err = getFileCommit(ctx, client, owner, repo, file, ref)
commit, err = getFileCommit(ctx, client, owner, repoName, file, ref)
}
if err != nil {
return diag.FromErr(err)
Expand Down Expand Up @@ -345,31 +373,20 @@ func resourceGithubRepositoryFileUpdate(ctx context.Context, d *schema.ResourceD

repo := d.Get("repository").(string)
file := d.Get("file").(string)
branch := d.Get("branch").(string)

ctx = tflog.SetField(ctx, "repository", repo)
ctx = tflog.SetField(ctx, "file", file)
ctx = tflog.SetField(ctx, "owner", owner)

if branch, ok := d.GetOk("branch"); ok {
tflog.Debug(ctx, "Using explicitly set branch", map[string]any{
"branch": branch.(string),
})
if err := checkRepositoryBranchExists(client, owner, repo, branch.(string)); err != nil {
if d.Get("autocreate_branch").(bool) {
if err := resourceGithubRepositoryFileCreateBranch(ctx, d, meta); err != nil {
return diag.FromErr(err)
}
} else {
return diag.FromErr(err)
}
}
}
ctx = tflog.SetField(ctx, "branch", branch)

opts := resourceGithubRepositoryFileOptions(d)

if *opts.Message == fmt.Sprintf("Add %s", file) {
opts.Message = github.Ptr(fmt.Sprintf("Update %s", file))
}

// TODO: Use UpdateFile if the file already exists
create, _, err := client.Repositories.CreateFile(ctx, owner, repo, file, opts)
Comment thread
deiga marked this conversation as resolved.
Outdated
if err != nil {
return diag.FromErr(err)
Expand All @@ -379,6 +396,14 @@ func resourceGithubRepositoryFileUpdate(ctx context.Context, d *schema.ResourceD
return diag.FromErr(err)
}

if d.HasChanges("repository", "file", "branch") {
Comment thread
stevehipwell marked this conversation as resolved.
newResourceID, err := buildID(repo, file, branch)
if err != nil {
return diag.FromErr(err)
}
d.SetId(newResourceID)
}

return resourceGithubRepositoryFileRead(ctx, d, meta)
}

Expand All @@ -395,22 +420,8 @@ func resourceGithubRepositoryFileDelete(ctx context.Context, d *schema.ResourceD
opts.Message = github.Ptr(fmt.Sprintf("Delete %s", file))
}

if b, ok := d.GetOk("branch"); ok {
tflog.Debug(ctx, "Using explicitly set branch", map[string]any{
"branch": b.(string),
})
if err := checkRepositoryBranchExists(client, owner, repo, b.(string)); err != nil {
if d.Get("autocreate_branch").(bool) {
if err := resourceGithubRepositoryFileCreateBranch(ctx, d, meta); err != nil {
return diag.FromErr(err)
}
} else {
return diag.FromErr(err)
}
}
branch := b.(string)
opts.Branch = github.Ptr(branch)
}
branch := d.Get("branch").(string)
opts.Branch = github.Ptr(branch)

_, _, err := client.Repositories.DeleteFile(ctx, owner, repo, file, opts)
return diag.FromErr(handleArchivedRepoDelete(err, "repository file", file, owner, repo))
Expand All @@ -427,28 +438,56 @@ func autoBranchDiffSuppressFunc(k, _, _ string, d *schema.ResourceData) bool {
}

func resourceGithubRepositoryFileImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
repoFilePath, branch, err := parseID2(d.Id())
repo, filePath, branch, err := parseID3(d.Id())
if err != nil {
return nil, fmt.Errorf("invalid ID specified. Supplied ID must be written as <repository>/<file path>:<branch>. %w", err)
return nil, fmt.Errorf("invalid ID specified. Supplied ID must be written as <repository>:<file path>: (when branch is default) or <repository>:<file path>:<branch>. %w", err)
}

client := meta.(*Owner).v3client
owner := meta.(*Owner).name
repo, file := splitRepoFilePath(repoFilePath)

opts := &github.RepositoryContentGetOptions{Ref: branch}
if err := d.Set("branch", branch); err != nil {
opts := &github.RepositoryContentGetOptions{}

repoInfo, _, err := client.Repositories.Get(ctx, owner, repo)
Comment thread
deiga marked this conversation as resolved.
Comment thread
deiga marked this conversation as resolved.
if err != nil {
return nil, err
}
fc, _, _, err := client.Repositories.GetContents(ctx, owner, repo, file, opts)
if branch == "" {
branch = repoInfo.GetDefaultBranch()
}

opts.Ref = branch

fc, _, _, err := client.Repositories.GetContents(ctx, owner, repo, filePath, opts)
if err != nil {
return nil, err
}
if fc == nil {
return nil, fmt.Errorf("file %s is not a file in repository %s/%s or repository is not readable", file, owner, repo)
return nil, fmt.Errorf("filePath %s is not a file in repository %s/%s or repository is not readable", filePath, owner, repo)
}

d.SetId(fmt.Sprintf("%s/%s", repo, file))
if err := d.Set("repository", repo); err != nil {
return nil, err
}
if err := d.Set("file", filePath); err != nil {
return nil, err
}

newResourceID, err := buildID(repo, filePath, branch)
if err != nil {
return nil, err
}
tflog.Debug(ctx, "Setting ID", map[string]any{
"id": newResourceID,
})
d.SetId(newResourceID)

if err := d.Set("branch", branch); err != nil {
return nil, err
}
if err := d.Set("repository_id", int(repoInfo.GetID())); err != nil {
return nil, err
}
if err = d.Set("overwrite_on_create", false); err != nil {
return nil, err
}
Expand Down
Loading