diff --git a/Makefile b/Makefile index c43daeb0..b60a0080 100644 --- a/Makefile +++ b/Makefile @@ -36,8 +36,8 @@ release: GOOS=windows GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_windows_amd64 install: build - mkdir -p ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} - mv ${BINARY} ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} + mkdir -p ~/.terraform.d/plugins/local/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} + mv ${BINARY} ~/.terraform.d/plugins/local/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} test: go test -i $(TEST) || exit 1 diff --git a/pkg/application/creation.go b/pkg/application/creation.go index 43246cd8..69aa298c 100644 --- a/pkg/application/creation.go +++ b/pkg/application/creation.go @@ -45,8 +45,16 @@ type Deployment struct { PrivateSSHKey *string } +func (d Deployment) GetCommit() string { + if d.Commit != nil { + return *d.Commit + } + return "" +} + type CreateRes struct { Application tmp.CreatAppResponse + Commit string // when there is a git URL to deploy but no commit SET } func (r *CreateRes) GetBuildFlavor() types.String { @@ -92,18 +100,13 @@ func CreateApp(ctx context.Context, req CreateReq) (*CreateRes, diag.Diagnostics } res.Application.Vhosts = *vhostsRes.Payload() - // Git Deployment - if req.Deployment != nil { - diags.Append(gitDeploy(ctx, *req.Deployment, req.Client, res.Application.DeployURL)...) - } - // Dependencies dependenciesWithAddonIDs, err := tmp.RealIDsToAddonIDs(ctx, req.Client, req.Organization, req.Dependencies...) if err != nil { diags.AddError("failed to get dependencies addon IDs", err.Error()) return nil, diags } - tflog.Debug(ctx, "[create] dependencies to link", map[string]any{"dependencies": req.Dependencies, "addonIds": dependenciesWithAddonIDs}) + tflog.Debug(ctx, "[create] mak to link", map[string]any{"dependencies": req.Dependencies, "addonIds": dependenciesWithAddonIDs}) for _, dependency := range dependenciesWithAddonIDs { // TODO: support another apps as dependency @@ -114,13 +117,23 @@ func CreateApp(ctx context.Context, req CreateReq) (*CreateRes, diag.Diagnostics } } + // Git Deployment + if req.Deployment != nil { + result := gitDeploy(ctx, *req.Deployment, res.Application.DeployURL, &diags) + if diags.HasError() { + return nil, diags + } + + tflog.Info(ctx, "### result.EffectivePush: %+v\n", map[string]any{"effectivePush": result.EffectivePush}) + tflog.Info(ctx, "### result.DeployedCommit: %+v\n", map[string]any{"deployedCommit": result.ResolvedCommitHash}) + res.Commit = result.ResolvedCommitHash + } + return res, diags } func UpdateApp(ctx context.Context, req UpdateReq) (*CreateRes, diag.Diagnostics) { diags := diag.Diagnostics{} - - // Application res := &CreateRes{} appRes := tmp.UpdateApp(ctx, req.Client, req.Organization, req.ID, req.Application) @@ -175,16 +188,17 @@ func UpdateApp(ctx context.Context, req UpdateReq) (*CreateRes, diag.Diagnostics // TODO: unlink unneeded deps // Git Deployment (when commit change) + hasBeenPushed := false if req.Deployment != nil { - diags.Append(gitDeploy(ctx, *req.Deployment, req.Client, res.Application.DeployURL)...) - if diags.HasError() { - return nil, diags - } + result := gitDeploy(ctx, *req.Deployment, res.Application.DeployURL, &diags) + hasBeenPushed = result.EffectivePush + res.Commit = result.ResolvedCommitHash } // trigger restart of the app if needed (when env change) + // don't trigger if we just "git push" // error id 4014 = cannot redeploy an application which has never been deployed yet (did you git push?) - if req.TriggerRestart { + if req.TriggerRestart && !hasBeenPushed { restartRes := tmp.RestartApp(ctx, req.Client, req.Organization, res.Application.ID) if restartRes.HasError() && !strings.Contains(restartRes.Error().Error(), "4014") { diags.AddError("failed to restart app", restartRes.Error().Error()) diff --git a/pkg/application/git.go b/pkg/application/git.go index 392b7573..799455df 100644 --- a/pkg/application/git.go +++ b/pkg/application/git.go @@ -16,35 +16,47 @@ import ( "github.com/go-git/go-git/v5/storage/memory" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-log/tflog" - "go.clever-cloud.dev/client" ) -func gitDeploy(ctx context.Context, d Deployment, cc *client.Client, cleverRemote string) diag.Diagnostics { - var diags diag.Diagnostics +type GitDeployResult struct { + ResolvedCommitHash string // filled only if no commit is provided to gitDeploy() + EffectivePush bool +} + +func gitDeploy(ctx context.Context, d Deployment, cleverRemote string, diags *diag.Diagnostics) GitDeployResult { + var result GitDeployResult for range 5 { - diags = _gitDeploy(ctx, d, cc, cleverRemote) + result = _gitDeploy(ctx, d, cleverRemote, diags) if !diags.HasError() { break } time.Sleep(3 * time.Second) } - return diags + return result } -func _gitDeploy(ctx context.Context, d Deployment, cc *client.Client, cleverRemote string) diag.Diagnostics { +// Clone, add a remote, checkout and push the code to clever +// EffectivePush is true if there is really something pushed +// ResolvedCommitHash is the commit hash that was pushed if no commit was provided +func _gitDeploy(ctx context.Context, d Deployment, cleverRemote string, diags *diag.Diagnostics) GitDeployResult { + result := GitDeployResult{EffectivePush: false} cleverRemote = strings.Replace(cleverRemote, "git+ssh", "https", 1) // switch protocol + tflog.Info(ctx, "DEPLOY", map[string]any{ + "repository": d.Repository, + "commit": d.GetCommit(), + }) - repo, diags := OpenOrClone(ctx, d.Repository, d.Commit) + repo := OpenOrClone(ctx, d.Repository, d.Commit, diags) if diags.HasError() { - return diags + return result } currentRef, err := repo.Head() if err != nil { diags.AddError("failed to get current ref", err.Error()) - return diags + return result } remoteOpts := &config.RemoteConfig{ @@ -59,7 +71,7 @@ func _gitDeploy(ctx context.Context, d Deployment, cc *client.Client, cleverRemo remote, err := repo.CreateRemote(remoteOpts) if err != nil { diags.AddError("failed to add clever remote", err.Error()) - return diags + return result } pushOptions := &git.PushOptions{ @@ -84,11 +96,11 @@ func _gitDeploy(ctx context.Context, d Deployment, cc *client.Client, cleverRemo commit, err := repo.CommitObject(plumbing.NewHash(refNameOrCommit)) if err == plumbing.ErrObjectNotFound { diags.AddError("requested commit not found", fmt.Sprintf("no commit '%s'", refNameOrCommit)) - return diags + return result } if err != nil { diags.AddError("failed to look for commit", err.Error()) - return diags + return result } refSpec = config.RefSpec(fmt.Sprintf("%s:%s", commit.Hash.String(), plumbing.Master)) @@ -101,11 +113,11 @@ func _gitDeploy(ctx context.Context, d Deployment, cc *client.Client, cleverRemo ref, err := repo.Storer.Reference(plumbing.ReferenceName(refNameOrCommit)) if err == plumbing.ErrReferenceNotFound { diags.AddError("requested reference not found", fmt.Sprintf("no reference named '%s'", refNameOrCommit)) - return diags + return result } if err != nil { diags.AddError("failed to get reference", err.Error()) - return diags + return result } refSpec = config.RefSpec(fmt.Sprintf("%s:%s", ref.Hash().String(), plumbing.Master)) @@ -113,10 +125,13 @@ func _gitDeploy(ctx context.Context, d Deployment, cc *client.Client, cleverRemo if err := refSpec.Validate(); err != nil { diags.AddError("failed to build ref spec to push", err.Error()) - return diags + return result } pushOptions.RefSpecs = []config.RefSpec{refSpec} + } else { + // send current commit + result.ResolvedCommitHash = currentRef.Hash().String() } tflog.Debug(ctx, "pushing...", map[string]any{ @@ -130,9 +145,11 @@ func _gitDeploy(ctx context.Context, d Deployment, cc *client.Client, cleverRemo } else { diags.AddError("failed to push to clever remote", err.Error()) } + } else { + result.EffectivePush = true } - return diags + return result } func IsSHA1(s string) bool { @@ -140,28 +157,25 @@ func IsSHA1(s string) bool { return err == nil && len(h) == sha1.Size } -func OpenOrClone(ctx context.Context, repoUrl string, commit *string) (*git.Repository, diag.Diagnostics) { +func OpenOrClone(ctx context.Context, repoUrl string, commit *string, diags *diag.Diagnostics) *git.Repository { if strings.HasPrefix(repoUrl, "file://") { - return open(repoUrl) + return open(repoUrl, diags) } - return clone(ctx, repoUrl, commit) + return clone(ctx, repoUrl, commit, diags) } -func open(repoUrl string) (*git.Repository, diag.Diagnostics) { - diags := diag.Diagnostics{} - +func open(repoUrl string, diags *diag.Diagnostics) *git.Repository { repo, err := git.PlainOpen(strings.TrimPrefix(repoUrl, "file://")) if err != nil { diags.AddError("failed to open repository", fmt.Sprintf("cannot open '%s': %s", repoUrl, err.Error())) - return nil, diags + return nil } - return repo, diags + return repo } -func clone(ctx context.Context, repoUrl string, commit *string) (*git.Repository, diag.Diagnostics) { - diags := diag.Diagnostics{} +func clone(ctx context.Context, repoUrl string, commit *string, diags *diag.Diagnostics) *git.Repository { fs := memory.NewStorage() wt := memfs.New() @@ -178,8 +192,8 @@ func clone(ctx context.Context, repoUrl string, commit *string) (*git.Repository r, err := git.CloneContext(ctx, fs, wt, cloneOpts) if err != nil { diags.AddError("failed to clone repository", err.Error()) - return nil, diags + return nil } - return r, diags + return r } diff --git a/pkg/attributes/blocks.go b/pkg/attributes/blocks.go index 2da56696..b95f9ca3 100644 --- a/pkg/attributes/blocks.go +++ b/pkg/attributes/blocks.go @@ -36,6 +36,8 @@ var blocks = map[string]schema.Block{ }, "commit": schema.StringAttribute{ Optional: true, + Required: false, + Computed: true, // if user provider repo but no commit, we will resolve last one when "git push" Description: "The git reference you want to deploy", MarkdownDescription: "Support multiple syntax like `refs/heads/[BRANCH]` or `[COMMIT]`, in most of the case, you can use `refs/heads/master`", Validators: []validator.String{ diff --git a/pkg/attributes/runtime.go b/pkg/attributes/runtime.go index e7dbcc2a..2f34d5dc 100644 --- a/pkg/attributes/runtime.go +++ b/pkg/attributes/runtime.go @@ -45,6 +45,18 @@ func (r Runtime) VHostsAsStrings(ctx context.Context, diags *diag.Diagnostics) [ return vhosts } +// Set commit in Deployment block if not empty +func (r *Runtime) SetCommit(commit string) { + if commit == "" { + return + } + + if r.Deployment == nil { + r.Deployment = &Deployment{} + } + r.Deployment.Commit = pkg.FromStr(commit) +} + // This attributes are used on several runtimes var runtimeCommon = map[string]schema.Attribute{ // client provided diff --git a/pkg/resources/docker/crud.go b/pkg/resources/docker/crud.go index fe701b6c..ab170b0d 100644 --- a/pkg/resources/docker/crud.go +++ b/pkg/resources/docker/crud.go @@ -125,9 +125,7 @@ func (r *ResourceDocker) Create(ctx context.Context, req resource.CreateRequest, // Read resource information func (r *ResourceDocker) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var state Docker - - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + state := helper.StateFrom[Docker](ctx, req.State, &resp.Diagnostics) if resp.Diagnostics.HasError() { return } @@ -151,6 +149,7 @@ func (r *ResourceDocker) Read(ctx context.Context, req resource.ReadRequest, res state.Region = pkg.FromStr(app.App.Zone) state.DeployURL = pkg.FromStr(app.App.DeployURL) state.BuildFlavor = app.GetBuildFlavor() + state.SetCommit(app.App.CommitID) vhosts := app.App.Vhosts.AsString() state.VHosts = pkg.FromSetString(vhosts, &resp.Diagnostics) diff --git a/pkg/resources/golang/crud.go b/pkg/resources/golang/crud.go index 2b439328..f6f1e450 100644 --- a/pkg/resources/golang/crud.go +++ b/pkg/resources/golang/crud.go @@ -150,6 +150,7 @@ func (r *ResourceGo) Read(ctx context.Context, req resource.ReadRequest, res *re state.BuildFlavor = appRes.GetBuildFlavor() state.StickySessions = pkg.FromBool(appRes.App.StickySessions) state.RedirectHTTPS = pkg.FromBool(application.ToForceHTTPS(appRes.App.ForceHTTPS)) + state.SetCommit(appRes.App.CommitID) vhosts := appRes.App.Vhosts.AsString() state.VHosts = pkg.FromSetString(vhosts, &res.Diagnostics) diff --git a/pkg/resources/java/crud.go b/pkg/resources/java/crud.go index e9d1d9a7..707701fa 100644 --- a/pkg/resources/java/crud.go +++ b/pkg/resources/java/crud.go @@ -148,6 +148,7 @@ func (r *ResourceJava) Read(ctx context.Context, req resource.ReadRequest, resp state.Region = pkg.FromStr(readRes.App.Zone) state.DeployURL = pkg.FromStr(readRes.App.DeployURL) state.BuildFlavor = readRes.GetBuildFlavor() + state.SetCommit(readRes.App.CommitID) vhosts := readRes.App.Vhosts.AsString() state.VHosts = pkg.FromSetString(vhosts, &resp.Diagnostics) diff --git a/pkg/resources/nodejs/crud.go b/pkg/resources/nodejs/crud.go index 6503c8aa..f8a53b21 100644 --- a/pkg/resources/nodejs/crud.go +++ b/pkg/resources/nodejs/crud.go @@ -148,6 +148,7 @@ func (r *ResourceNodeJS) Read(ctx context.Context, req resource.ReadRequest, res state.MaxInstanceCount = basetypes.NewInt64Value(int64(appRes.App.Instance.MaxInstances)) state.SmallestFlavor = pkg.FromStr(appRes.App.Instance.MinFlavor.Name) state.BiggestFlavor = pkg.FromStr(appRes.App.Instance.MaxFlavor.Name) + state.SetCommit(appRes.App.CommitID) vhosts := appRes.App.Vhosts.AsString() state.VHosts = pkg.FromSetString(vhosts, &resp.Diagnostics) diff --git a/pkg/resources/php/crud.go b/pkg/resources/php/crud.go index 7d76081c..fb83af88 100644 --- a/pkg/resources/php/crud.go +++ b/pkg/resources/php/crud.go @@ -150,6 +150,7 @@ func (r *ResourcePHP) Read(ctx context.Context, req resource.ReadRequest, resp * state.Region = pkg.FromStr(appPHP.App.Zone) state.DeployURL = pkg.FromStr(appPHP.App.DeployURL) state.BuildFlavor = appPHP.GetBuildFlavor() + state.SetCommit(appPHP.App.CommitID) vhosts := appPHP.App.Vhosts.AsString() state.VHosts = pkg.FromSetString(vhosts, &resp.Diagnostics) diff --git a/pkg/resources/play2/crud.go b/pkg/resources/play2/crud.go index 377454d2..6775313b 100644 --- a/pkg/resources/play2/crud.go +++ b/pkg/resources/play2/crud.go @@ -148,6 +148,7 @@ func (r *ResourcePlay2) Read(ctx context.Context, req resource.ReadRequest, resp state.Region = pkg.FromStr(readRes.App.Zone) state.DeployURL = pkg.FromStr(readRes.App.DeployURL) state.BuildFlavor = readRes.GetBuildFlavor() + state.SetCommit(readRes.App.CommitID) vhosts := readRes.App.Vhosts.AsString() state.VHosts = pkg.FromSetString(vhosts, &resp.Diagnostics) diff --git a/pkg/resources/python/crud.go b/pkg/resources/python/crud.go index 3ad9a330..76618b71 100644 --- a/pkg/resources/python/crud.go +++ b/pkg/resources/python/crud.go @@ -113,6 +113,14 @@ func (r *ResourcePython) Create(ctx context.Context, req resource.CreateRequest, } } + tflog.Info(ctx, "plan.Deployment: %+v\n", map[string]any{"deployment": plan.Deployment}) + //fmt.Printf("plan.Deployment.Commit: %+v\n", plan.Deployment.Commit) + tflog.Info(ctx, "createRes.Commit: %+v\n", map[string]any{"commit": createRes.Commit}) + if (plan.Deployment == nil || plan.Deployment.Commit.IsNull()) && createRes.Commit != "" { + tflog.Info(ctx, "############################ Setting commit to", map[string]any{"commit": createRes.Commit}) + plan.SetCommit(createRes.Commit) + } + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) if resp.Diagnostics.HasError() { return @@ -139,6 +147,7 @@ func (r *ResourcePython) Read(ctx context.Context, req resource.ReadRequest, res } state.DeployURL = pkg.FromStr(appRes.App.DeployURL) + state.SetCommit(appRes.App.CommitID) vhosts := appRes.App.Vhosts.AsString() state.VHosts = pkg.FromSetString(vhosts, &resp.Diagnostics) diff --git a/pkg/resources/python/schema.go b/pkg/resources/python/schema.go index 2774941b..dd5bfce9 100644 --- a/pkg/resources/python/schema.go +++ b/pkg/resources/python/schema.go @@ -3,6 +3,7 @@ package python import ( "context" _ "embed" + "fmt" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -74,9 +75,15 @@ func (py Python) toDeployment(gitAuth *http.BasicAuth) *application.Deployment { return nil } - return &application.Deployment{ + d := &application.Deployment{ Repository: py.Deployment.Repository.ValueString(), - Commit: py.Deployment.Commit.ValueStringPointer(), CleverGitAuth: gitAuth, } + + if !py.Deployment.Commit.IsNull() && !py.Deployment.Commit.IsUnknown() { + d.Commit = py.Deployment.Commit.ValueStringPointer() + } + fmt.Printf("############### py.Deployment.Commit: %+v\n", d) + + return d } diff --git a/pkg/resources/scala/crud.go b/pkg/resources/scala/crud.go index 50642f6e..fd541639 100644 --- a/pkg/resources/scala/crud.go +++ b/pkg/resources/scala/crud.go @@ -152,6 +152,7 @@ func (r *ResourceScala) Read(ctx context.Context, req resource.ReadRequest, resp state.Region = pkg.FromStr(readRes.App.Zone) state.DeployURL = pkg.FromStr(readRes.App.DeployURL) state.BuildFlavor = readRes.GetBuildFlavor() + state.SetCommit(readRes.App.CommitID) vhosts := readRes.App.Vhosts.AsString() state.VHosts = pkg.FromSetString(vhosts, &resp.Diagnostics) diff --git a/pkg/resources/static/crud.go b/pkg/resources/static/crud.go index 981251f1..8a695d30 100644 --- a/pkg/resources/static/crud.go +++ b/pkg/resources/static/crud.go @@ -153,6 +153,7 @@ func (r *ResourceStatic) Read(ctx context.Context, req resource.ReadRequest, res state.Region = pkg.FromStr(readRes.App.Zone) state.DeployURL = pkg.FromStr(readRes.App.DeployURL) state.BuildFlavor = readRes.GetBuildFlavor() + state.SetCommit(readRes.App.CommitID) vhosts := readRes.App.Vhosts.AsString() state.VHosts = pkg.FromSetString(vhosts, &resp.Diagnostics)