From 176022abe6430ec4d72e90706c3e29b752e66008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20GARNIER?= Date: Sat, 22 Jan 2022 01:01:07 +0100 Subject: [PATCH] Create commit resource --- LICENSE | 8 +- Makefile | 69 ----------------- README.md | 2 +- provider/data_file.go | 8 +- provider/resource_commit.go | 129 +++++++++++++++++++++++++++++--- provider/resource_file.go | 34 ++++----- provider/resource_repository.go | 26 +++---- 7 files changed, 159 insertions(+), 117 deletions(-) delete mode 100644 Makefile diff --git a/LICENSE b/LICENSE index e06d208..d645695 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ -Apache License + + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -178,7 +179,7 @@ Apache License APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" + boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +187,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -199,4 +200,3 @@ Apache License WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - diff --git a/Makefile b/Makefile deleted file mode 100644 index 0a3e883..0000000 --- a/Makefile +++ /dev/null @@ -1,69 +0,0 @@ -SWEEP?=us-east-1,us-west-2 -TEST?=./... -GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor) -PKG_NAME=gitfile -WEBSITE_REPO=github.com/hashicorp/terraform-website - -default: build - -build: fmtcheck - go install - -sweep: - @echo "WARNING: This will destroy infrastructure. Use only in development accounts." - go test $(TEST) -v -sweep=$(SWEEP) $(SWEEPARGS) - -test: fmtcheck - go test $(TEST) -timeout=30s -parallel=4 - -testacc: fmtcheck - TF_ACC=1 go test $(TEST) -v -parallel 20 $(TESTARGS) -timeout 120m - -fmt: - @echo "==> Fixing source code with gofmt..." - gofmt -s -w ./$(PKG_NAME) - -# Currently required by tf-deploy compile -fmtcheck: - @sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'" - -websitefmtcheck: - @sh -c "'$(CURDIR)/scripts/websitefmtcheck.sh'" - -lint: - @echo "==> Checking source code against linters..." - @GOGC=30 golangci-lint run ./$(PKG_NAME) - @tfproviderlint -c 1 -S001 -S002 -S003 -S004 -S005 ./$(PKG_NAME) - -tools: - GO111MODULE=on go install github.com/bflad/tfproviderlint/cmd/tfproviderlint - GO111MODULE=on go install github.com/client9/misspell/cmd/misspell - GO111MODULE=on go install github.com/golangci/golangci-lint/cmd/golangci-lint - -test-compile: - @if [ "$(TEST)" = "./..." ]; then \ - echo "ERROR: Set TEST to a specific package. For example,"; \ - echo " make test-compile TEST=./$(PKG_NAME)"; \ - exit 1; \ - fi - go test -c $(TEST) $(TESTARGS) - -website: -ifeq (,$(wildcard $(GOPATH)/src/$(WEBSITE_REPO))) - echo "$(WEBSITE_REPO) not found in your GOPATH (necessary for layouts and assets), get-ting..." - git clone https://$(WEBSITE_REPO) $(GOPATH)/src/$(WEBSITE_REPO) -endif - @$(MAKE) -C $(GOPATH)/src/$(WEBSITE_REPO) website-provider PROVIDER_PATH=$(shell pwd) PROVIDER_NAME=$(PKG_NAME) - -website-lint: - @echo "==> Checking website against linters..." - @misspell -error -source=text website/ - -website-test: -ifeq (,$(wildcard $(GOPATH)/src/$(WEBSITE_REPO))) - echo "$(WEBSITE_REPO) not found in your GOPATH (necessary for layouts and assets), get-ting..." - git clone https://$(WEBSITE_REPO) $(GOPATH)/src/$(WEBSITE_REPO) -endif - @$(MAKE) -C $(GOPATH)/src/$(WEBSITE_REPO) website-provider-test PROVIDER_PATH=$(shell pwd) PROVIDER_NAME=$(PKG_NAME) - -.PHONY: build sweep test testacc fmt fmtcheck lint tools test-compile website website-lint website-test diff --git a/README.md b/README.md index ea1ab5c..d940600 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ resource "git_commit" "hello_world" { message = "Create file.txt" add { - path = git_file.hello_world.path + file = git_file.hello_world.path } } ``` diff --git a/provider/data_file.go b/provider/data_file.go index 9719d8d..a5862d2 100644 --- a/provider/data_file.go +++ b/provider/data_file.go @@ -38,7 +38,7 @@ func dataFileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) // Open already cloned repository _, worktree, err := getRepository(dir) if err != nil { - return diag.Errorf("failed to open repository: %s", err.Error()) + return diag.Errorf("failed to open repository: %s", err) } d.SetId(filepath.Join(dir, path)) @@ -46,18 +46,18 @@ func dataFileRead(ctx context.Context, d *schema.ResourceData, meta interface{}) // Open, read then close file file, err := worktree.Filesystem.Open(path) if err != nil { - return diag.Errorf("failed to open file: %s", err.Error()) + return diag.Errorf("failed to open file: %s", err) } content, err := io.ReadAll(file) if err != nil { - return diag.Errorf("failed to read file: %s", err.Error()) + return diag.Errorf("failed to read file: %s", err) } d.Set("content", string(content)) err = file.Close() if err != nil { - return diag.Errorf("failed to close file: %s", err.Error()) + return diag.Errorf("failed to close file: %s", err) } return nil diff --git a/provider/resource_commit.go b/provider/resource_commit.go index d5fb1dd..4b1663c 100644 --- a/provider/resource_commit.go +++ b/provider/resource_commit.go @@ -3,6 +3,8 @@ package provider import ( "context" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing/object" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -11,32 +13,141 @@ func resourceCommit() *schema.Resource { return &schema.Resource{ CreateContext: resourceCommitCreate, ReadContext: resourceCommitRead, - // UpdateContext: resourceCommitUpdate, - DeleteContext: resourceCommitDelete, + DeleteContext: schema.NoopContext, Schema: map[string]*schema.Schema{ + "repository": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "branch": { + Type: schema.TypeString, + Optional: true, + Default: "main", + ForceNew: true, + }, "message": { Type: schema.TypeString, Optional: true, - Default: "Created by Terraform", + Default: "Committed with Terraform", ForceNew: true, }, + "add": { + Type: schema.TypeList, + Required: true, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "file": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "pattern": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + }, + }, + ForceNew: true, + }, + + "sha": { + Type: schema.TypeString, + Computed: true, + }, }, } } func resourceCommitCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + dir := d.Get("repository").(string) + // branch := d.Get("branch").(string) + message := d.Get("message").(string) + items := d.Get("add").([]interface{}) + + // Open already cloned repository + _, worktree, err := getRepository(dir) + if err != nil { + return diag.Errorf("failed to open repository: %s", err) + } + + // Stage files + for _, item := range items { + if pattern, ok := item.(map[string]interface{})["pattern"]; ok { + err := worktree.AddGlob(pattern.(string)) + if err != nil { + return diag.Errorf("failed to stage pattern %s: %s", pattern, err) + } + } + if file, ok := item.(map[string]interface{})["file"]; ok { + _, err := worktree.Add(file.(string)) + if err != nil { + return diag.Errorf("failed to stage file %s: %s", file, err) + } + } + } + + // Commit + sha, err := worktree.Commit(message, &git.CommitOptions{}) + if err != nil { + return diag.Errorf("failed to commit: %s", err) + } + + d.SetId(sha.String()) + d.Set("sha", sha.String()) + + // Push + // err = repo.Push(&git.PushOptions{ + // RefSpecs: []config.RefSpec{ + // config.RefSpec(fmt.Sprintf("+HEAD:refs/remotes/origin/%s", branch)), + // }, + // Force: true, + // }) + // if err != nil { + // return diag.Errorf("failed to push: %s", err) + // } + return nil } func resourceCommitRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - return nil -} + dir := d.Get("repository").(string) + items := d.Get("add").([]interface{}) + + // Open already cloned repository + _, worktree, err := getRepository(dir) + if err != nil { + return diag.Errorf("failed to open repository: %s", err) + } -// func resourceCommitUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { -// return nil -// } + // Stage files + for _, item := range items { + if pattern, ok := item.(map[string]interface{})["pattern"]; ok { + err := worktree.AddGlob(pattern.(string)) + if err != nil { + return diag.Errorf("failed to stage pattern %s: %s", pattern, err) + } + } + if file, ok := item.(map[string]interface{})["file"]; ok { + _, err := worktree.Add(file.(string)) + if err != nil && err.Error() != object.ErrEntryNotFound.Error() { + return diag.Errorf("failed to stage file %s: %s", file, err) + } + } + } + + // Check if worktree is clean + status, err := worktree.Status() + if err != nil { + return diag.Errorf("failed to compute worktree status: %s", err) + } + if status.IsClean() { + d.SetId("") + return nil + } -func resourceCommitDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { return nil } diff --git a/provider/resource_file.go b/provider/resource_file.go index 5587192..14c70c9 100644 --- a/provider/resource_file.go +++ b/provider/resource_file.go @@ -46,7 +46,7 @@ func resourceFileCreate(ctx context.Context, d *schema.ResourceData, meta interf // Open already cloned repository _, worktree, err := getRepository(dir) if err != nil { - return diag.Errorf("failed to open repository: %s", err.Error()) + return diag.Errorf("failed to open repository: %s", err) } d.SetId(filepath.Join(dir, path)) @@ -54,17 +54,17 @@ func resourceFileCreate(ctx context.Context, d *schema.ResourceData, meta interf // Create, write then close file file, err := worktree.Filesystem.Create(path) if err != nil { - return diag.Errorf("failed to create file: %s", err.Error()) + return diag.Errorf("failed to create file: %s", err) } _, err = io.WriteString(file, content) if err != nil { - return diag.Errorf("failed to write to file: %s", err.Error()) + return diag.Errorf("failed to write to file: %s", err) } err = file.Close() if err != nil { - return diag.Errorf("failed to close file: %s", err.Error()) + return diag.Errorf("failed to close file: %s", err) } return nil @@ -80,29 +80,29 @@ func resourceFileRead(ctx context.Context, d *schema.ResourceData, meta interfac d.SetId("") return nil } else if err != nil { - return diag.Errorf("failed to open repository: %s", err.Error()) + return diag.Errorf("failed to open repository: %s", err) } - d.SetId(filepath.Join(dir, path)) - // Open, read then close file file, err := worktree.Filesystem.Open(path) - if os.IsNotExist(err) { + if err != nil && os.IsNotExist(err) { d.SetId("") return nil } else if err != nil { - return diag.Errorf("failed to open file: %s", err.Error()) + return diag.Errorf("failed to open file: %s", err) } + d.SetId(filepath.Join(dir, path)) + content, err := io.ReadAll(file) if err != nil { - return diag.Errorf("failed to read file: %s", err.Error()) + return diag.Errorf("failed to read file: %s", err) } d.Set("content", string(content)) err = file.Close() if err != nil { - return diag.Errorf("failed to close file: %s", err.Error()) + return diag.Errorf("failed to close file: %s", err) } return nil @@ -116,7 +116,7 @@ func resourceFileUpdate(ctx context.Context, d *schema.ResourceData, meta interf // Open already cloned repository _, worktree, err := getRepository(dir) if err != nil { - return diag.Errorf("failed to open repository: %s", err.Error()) + return diag.Errorf("failed to open repository: %s", err) } d.SetId(filepath.Join(dir, path)) @@ -124,17 +124,17 @@ func resourceFileUpdate(ctx context.Context, d *schema.ResourceData, meta interf // Truncate, write then close file file, err := worktree.Filesystem.Create(path) if err != nil { - return diag.Errorf("failed to truncate file: %s", err.Error()) + return diag.Errorf("failed to truncate file: %s", err) } _, err = io.WriteString(file, content) if err != nil { - return diag.Errorf("failed to write to file: %s", err.Error()) + return diag.Errorf("failed to write to file: %s", err) } err = file.Close() if err != nil { - return diag.Errorf("failed to close file: %s", err.Error()) + return diag.Errorf("failed to close file: %s", err) } return nil @@ -147,7 +147,7 @@ func resourceFileDelete(ctx context.Context, d *schema.ResourceData, meta interf // Open already cloned repository _, worktree, err := getRepository(dir) if err != nil { - return diag.Errorf("failed to open repository: %s", err.Error()) + return diag.Errorf("failed to open repository: %s", err) } d.SetId(filepath.Join(dir, path)) @@ -155,7 +155,7 @@ func resourceFileDelete(ctx context.Context, d *schema.ResourceData, meta interf // Delete file err = worktree.Filesystem.Remove(path) if err != nil { - return diag.Errorf("failed to remove file: %s", err.Error()) + return diag.Errorf("failed to remove file: %s", err) } return nil diff --git a/provider/resource_repository.go b/provider/resource_repository.go index e205136..c4777ea 100644 --- a/provider/resource_repository.go +++ b/provider/resource_repository.go @@ -98,7 +98,7 @@ func resourceRepositoryCreate(ctx context.Context, d *schema.ResourceData, meta var err error dir, err = ioutil.TempDir("/tmp", "terraform-provider-git") if err != nil { - return diag.Errorf("failed to create temporary directory: %s", err.Error()) + return diag.Errorf("failed to create temporary directory: %s", err) } d.Set("dir", dir) @@ -111,7 +111,7 @@ func resourceRepositoryCreate(ctx context.Context, d *schema.ResourceData, meta Depth: 1, }) if err != nil { - return diag.Errorf("failed to clone repository: %s", err.Error()) + return diag.Errorf("failed to clone repository: %s", err) } d.SetId(url) @@ -119,12 +119,12 @@ func resourceRepositoryCreate(ctx context.Context, d *schema.ResourceData, meta // Resolve then checkout the specified ref hash, err := repo.ResolveRevision(plumbing.Revision(ref)) if err != nil { - return diag.Errorf("failed to resolve ref %s: %s", ref, err.Error()) + return diag.Errorf("failed to resolve ref %s: %s", ref, err) } worktree, err := repo.Worktree() if err != nil { - return diag.Errorf("failed to get worktree: %s", err.Error()) + return diag.Errorf("failed to get worktree: %s", err) } err = worktree.Checkout(&gogit.CheckoutOptions{ @@ -132,7 +132,7 @@ func resourceRepositoryCreate(ctx context.Context, d *schema.ResourceData, meta Force: true, }) if err != nil { - return diag.Errorf("failed to checkout hash %s: %s", hash.String(), err.Error()) + return diag.Errorf("failed to checkout hash %s: %s", hash.String(), err) } return setData(repo, d) @@ -144,7 +144,7 @@ func resourceRepositoryUpdate(ctx context.Context, d *schema.ResourceData, meta stat, err := os.Stat(dir) if err != nil && !os.IsNotExist(err) { - return diag.Errorf("failed to open directory: %s", err.Error()) + return diag.Errorf("failed to open directory: %s", err) } else if (err != nil && os.IsNotExist(err)) || !stat.IsDir() { // Remove resource from state d.SetId("") @@ -154,13 +154,13 @@ func resourceRepositoryUpdate(ctx context.Context, d *schema.ResourceData, meta // Open already cloned repository repo, worktree, err := getRepository(dir) if err != nil { - return diag.Errorf("failed to open repository: %s", err.Error()) + return diag.Errorf("failed to open repository: %s", err) } // Resolve the specified ref hash, err := repo.ResolveRevision(plumbing.Revision(ref)) if err != nil { - return diag.Errorf("failed to resolve ref %s: %s", ref, err.Error()) + return diag.Errorf("failed to resolve ref %s: %s", ref, err) } // Fetch origin updates @@ -171,7 +171,7 @@ func resourceRepositoryUpdate(ctx context.Context, d *schema.ResourceData, meta }) if err != nil && !errors.Is(err, gogit.NoErrAlreadyUpToDate) && !errors.Is(err, transport.ErrEmptyUploadPackRequest) { // TODO: https://github.com/go-git/go-git/issues/328 - return diag.Errorf("failed to fetch updates: %s", err.Error()) + return diag.Errorf("failed to fetch updates: %s", err) } err = worktree.Checkout(&gogit.CheckoutOptions{ @@ -179,7 +179,7 @@ func resourceRepositoryUpdate(ctx context.Context, d *schema.ResourceData, meta Force: true, }) if err != nil { - return diag.Errorf("failed to checkout hash %s: %s", hash.String(), err.Error()) + return diag.Errorf("failed to checkout hash %s: %s", hash.String(), err) } return setData(repo, d) @@ -198,7 +198,7 @@ func setData(repo *gogit.Repository, d *schema.ResourceData) diag.Diagnostics { // Set the HEAD hash output head, err := repo.Head() if err != nil { - return diag.Errorf("failed to list branches: %s", err.Error()) + return diag.Errorf("failed to list branches: %s", err) } d.Set("head", []map[string]string{ { @@ -209,7 +209,7 @@ func setData(repo *gogit.Repository, d *schema.ResourceData) diag.Diagnostics { // Set the branches list output branches, err := repo.Branches() if err != nil { - return diag.Errorf("failed to list branches: %s", err.Error()) + return diag.Errorf("failed to list branches: %s", err) } var branchesData []map[string]string branches.ForEach(func(branch *plumbing.Reference) error { @@ -225,7 +225,7 @@ func setData(repo *gogit.Repository, d *schema.ResourceData) diag.Diagnostics { // Set the tags list output tags, err := repo.Tags() if err != nil { - return diag.Errorf("failed to list tags: %s", err.Error()) + return diag.Errorf("failed to list tags: %s", err) } var tagsData []map[string]string tags.ForEach(func(tag *plumbing.Reference) error {