Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
62 changes: 53 additions & 9 deletions github/resource_github_repository_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"net/url"
"strings"

"github.com/google/go-github/v82/github"
"github.com/hashicorp/terraform-plugin-log/tflog"
Expand All @@ -23,6 +24,15 @@ 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{
Expand All @@ -47,6 +57,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 @@ -175,6 +186,14 @@ func resourceGithubRepositoryFileCreate(ctx context.Context, d *schema.ResourceD
}
}
checkOpt.Ref = branch.(string)
} else {
repoInfo, _, err := client.Repositories.Get(ctx, owner, repo)
if err != nil {
return diag.FromErr(err)
}
Comment thread
deiga marked this conversation as resolved.
Outdated
if err := d.Set("branch", repoInfo.GetDefaultBranch()); err != nil {
return diag.FromErr(err)
}
Comment thread
deiga marked this conversation as resolved.
Outdated
}

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

d.SetId(fmt.Sprintf("%s/%s", repo, file))
branch := d.Get("branch").(string)
Comment thread
deiga marked this conversation as resolved.
Outdated

d.SetId(fmt.Sprintf("%s/%s:%s", repo, file, branch))
Comment thread
deiga marked this conversation as resolved.
Outdated
if err = d.Set("commit_sha", create.GetSHA()); err != nil {
return diag.FromErr(err)
}
Expand All @@ -227,7 +248,11 @@ func resourceGithubRepositoryFileRead(ctx context.Context, d *schema.ResourceDat
client := meta.(*Owner).v3client
owner := meta.(*Owner).name

repo, file := splitRepoFilePath(d.Id())
repoFilePath, _, err := parseID2(d.Id())
if err != nil {
return diag.FromErr(err)
}
repo, file := splitRepoFilePath(repoFilePath)
ctx = tflog.SetField(ctx, "repository", repo)
ctx = tflog.SetField(ctx, "file", file)
ctx = tflog.SetField(ctx, "owner", owner)
Expand Down Expand Up @@ -427,19 +452,36 @@ 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())
if err != nil {
return nil, fmt.Errorf("invalid ID specified. Supplied ID must be written as <repository>/<file path>:<branch>. %w", err)
importIDParts := strings.Split(d.Id(), idSeparator)
Comment thread
deiga marked this conversation as resolved.
Outdated

if len(importIDParts) > 2 {
return nil, fmt.Errorf("invalid ID specified. Supplied ID must be written as <repository>/<file path> (when branch is \"main\") or <repository>/<file path>:<branch>")
}
repoFilePath := importIDParts[0]

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 {
return nil, err
opts := &github.RepositoryContentGetOptions{}

if len(importIDParts) == 2 {
branch := importIDParts[1]
opts.Ref = branch
if err := d.Set("branch", branch); err != nil {
return nil, err
}
} else {
repoInfo, _, err := client.Repositories.Get(ctx, owner, repo)
if err != nil {
return nil, err
}
defaultBranch := repoInfo.GetDefaultBranch()
if err := d.Set("branch", defaultBranch); err != nil {
return nil, err
}
}

fc, _, _, err := client.Repositories.GetContents(ctx, owner, repo, file, opts)
if err != nil {
return nil, err
Expand All @@ -448,7 +490,9 @@ func resourceGithubRepositoryFileImport(ctx context.Context, d *schema.ResourceD
return nil, fmt.Errorf("file %s is not a file in repository %s/%s or repository is not readable", file, owner, repo)
}

d.SetId(fmt.Sprintf("%s/%s", repo, file))
branch := d.Get("branch").(string)

d.SetId(fmt.Sprintf("%s/%s:%s", repo, file, branch))
if err = d.Set("overwrite_on_create", false); err != nil {
return nil, err
}
Expand Down
117 changes: 117 additions & 0 deletions github/resource_github_repository_file_migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package github

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceGithubRepositoryFileV0() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"file": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"content": {
Type: schema.TypeString,
Required: true,
},
"branch": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
"ref": {
Type: schema.TypeString,
Computed: true,
ForceNew: true,
},
"commit_sha": {
Type: schema.TypeString,
Computed: true,
},
"commit_message": {
Type: schema.TypeString,
Optional: true,
Computed: true,
},
"commit_author": {
Type: schema.TypeString,
Optional: true,
RequiredWith: []string{"commit_email"},
},
"commit_email": {
Type: schema.TypeString,
Optional: true,
RequiredWith: []string{"commit_author"},
},
"sha": {
Type: schema.TypeString,
Computed: true,
},
"overwrite_on_create": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"autocreate_branch": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"autocreate_branch_source_branch": {
Type: schema.TypeString,
Default: "main",
Optional: true,
RequiredWith: []string{"autocreate_branch"},
},
"autocreate_branch_source_sha": {
Type: schema.TypeString,
Optional: true,
Computed: true,
RequiredWith: []string{"autocreate_branch"},
},
},
}
}

func resourceGithubRepositoryFileStateUpgradeV0(ctx context.Context, rawState map[string]any, m any) (map[string]any, error) {
tflog.Debug(ctx, "GitHub Repository File State before migration", map[string]any{
"rawState": rawState,
})

// If branch is missing or empty, fetch the default branch from the repository
if branch, ok := rawState["branch"].(string); !ok || branch == "" {
meta := m.(*Owner)
client := meta.v3client
owner := meta.name

repoName, ok := rawState["repository"].(string)
if !ok {
return nil, fmt.Errorf("repository not found or is not a string")
}

repo, _, err := client.Repositories.Get(ctx, owner, repoName)
if err != nil {
return nil, fmt.Errorf("failed to retrieve repository %s: %w", repoName, err)
}

rawState["branch"] = repo.GetDefaultBranch()
}

rawState["id"] = fmt.Sprintf("%s/%s:%s", rawState["repository"], rawState["file"], rawState["branch"])

tflog.Debug(ctx, "GitHub Repository File State after migration", map[string]any{
"rawState": rawState,
})
return rawState, nil
}
112 changes: 112 additions & 0 deletions github/resource_github_repository_file_migration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package github

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
)

func Test_resourceGithubRepositoryFileStateUpgradeV0(t *testing.T) {
t.Parallel()

for _, d := range []struct {
testName string
rawState map[string]any
want map[string]any
shouldError bool
}{
{
testName: "preserves_existing_branch",
rawState: map[string]any{
"id": "test-repo/path/to/file.txt",
"repository": "test-repo",
"file": "path/to/file.txt",
"content": "file content",
"branch": "main",
"commit_sha": "abc123",
"sha": "def456",
"overwrite_on_create": false,
},
want: map[string]any{
"id": "test-repo/path/to/file.txt:main",
"repository": "test-repo",
"file": "path/to/file.txt",
"content": "file content",
"branch": "main",
"commit_sha": "abc123",
"sha": "def456",
"overwrite_on_create": false,
},
shouldError: false,
},
{
testName: "preserves_custom_branch",
rawState: map[string]any{
"id": "test-repo/README.md",
"repository": "test-repo",
"file": "README.md",
"content": "# README",
"branch": "develop",
},
want: map[string]any{
"id": "test-repo/README.md:develop",
"repository": "test-repo",
"file": "README.md",
"content": "# README",
"branch": "develop",
},
shouldError: false,
},
// TODO: Enable this test once we have a pattern to create a mock client for the test.
// {
// testName: "migrates_with_missing_branch",
// rawState: map[string]any{
// "id": "test-repo/path/to/file.txt",
// "repository": "test-repo",
// "file": "path/to/file.txt",
// "content": "file content",
// },
// want: map[string]any{
// "id": "test-repo/path/to/file.txt:main",
// "repository": "test-repo",
// "file": "path/to/file.txt",
// "content": "file content",
// "branch": "main", // fetched from API
// },
// shouldError: false,
// },
// TODO: Enable this test once we have a pattern to create a mock client for the test.
// {
// testName: "migrates_with_empty_branch",
// rawState: map[string]any{
// "id": "test-repo/path/to/file.txt",
// "repository": "test-repo",
// "file": "path/to/file.txt",
// "content": "file content",
// "branch": "",
// },
// want: map[string]any{
// "id": "test-repo/path/to/file.txt:main",
// "repository": "test-repo",
// "file": "path/to/file.txt",
// "content": "file content",
// "branch": "main", // fetched from API
// },
// shouldError: false,
// },
} {
t.Run(d.testName, func(t *testing.T) {
t.Parallel()

got, err := resourceGithubRepositoryFileStateUpgradeV0(context.Background(), d.rawState, nil)
if (err != nil) != d.shouldError {
t.Fatalf("unexpected error state: got error %v, shouldError %v", err, d.shouldError)
}

if diff := cmp.Diff(got, d.want); diff != "" && !d.shouldError {
t.Fatalf("got %+v, want %+v, diff %s", got, d.want, diff)
}
})
}
}
Loading