From 5559450aa3968a84850c629ae3daffcb6ee59c31 Mon Sep 17 00:00:00 2001 From: Dario Ferrer Date: Fri, 22 Apr 2022 17:56:30 +0200 Subject: [PATCH] GitSource: added git source deployment Followed the API documentation https://vercel.com/docs/rest-api#endpoints/deployments/create-a-new-deployment --- client/deployment_create.go | 25 +++++++++------ docs/resources/deployment.md | 13 +++++++- vercel/resource_deployment.go | 47 +++++++++++++++++++++++++++-- vercel/resource_deployment_model.go | 32 ++++++++++++++++++++ vercel/resource_deployment_test.go | 35 +++++++++++++++++++++ 5 files changed, 140 insertions(+), 12 deletions(-) diff --git a/client/deployment_create.go b/client/deployment_create.go index 5ac38fc5..4a029f38 100644 --- a/client/deployment_create.go +++ b/client/deployment_create.go @@ -20,6 +20,11 @@ type DeploymentFile struct { Sha string `json:"sha"` Size int `json:"size"` } +type GitSource struct { + RepoId string `json:"repoId"` + Ref string `json:"ref"` + Type string `json:"type"` +} // CreateDeploymentRequest defines the request the Vercel API expects in order to create a deployment. type CreateDeploymentRequest struct { @@ -35,6 +40,7 @@ type CreateDeploymentRequest struct { Regions []string `json:"regions,omitempty"` Routes []interface{} `json:"routes,omitempty"` Target string `json:"target,omitempty"` + GitSource *GitSource `json:"gitSource,omitempty"` } // DeploymentResponse defines the response the Vercel API returns when a deployment is created or updated. @@ -59,15 +65,16 @@ type DeploymentResponse struct { Build struct { Environment []string `json:"env"` } `json:"build"` - AliasAssigned bool `json:"aliasAssigned"` - ChecksConclusion string `json:"checksConclusion"` - ErrorCode string `json:"errorCode"` - ErrorMessage string `json:"errorMessage"` - ID string `json:"id"` - ProjectID string `json:"projectId"` - ReadyState string `json:"readyState"` - Target *string `json:"target"` - URL string `json:"url"` + AliasAssigned bool `json:"aliasAssigned"` + ChecksConclusion string `json:"checksConclusion"` + ErrorCode string `json:"errorCode"` + ErrorMessage string `json:"errorMessage"` + ID string `json:"id"` + ProjectID string `json:"projectId"` + ReadyState string `json:"readyState"` + Target *string `json:"target"` + URL string `json:"url"` + GitSource GitSource `json:"gitSource"` } // IsComplete is used to determine whether a deployment is still processing, or whether it is fully done. diff --git a/docs/resources/deployment.md b/docs/resources/deployment.md index 009d2a36..b28e9fa4 100644 --- a/docs/resources/deployment.md +++ b/docs/resources/deployment.md @@ -63,13 +63,14 @@ resource "vercel_deployment" "example" { ### Required -- `files` (Map of String) A map of files to be uploaded for the deployment. This should be provided by a `vercel_project_directory` or `vercel_file` data source. - `project_id` (String) The project ID to add the deployment to. ### Optional - `delete_on_destroy` (Boolean) Set to true to hard delete the Vercel deployment when destroying the Terraform resource. If unspecified, deployments are retained indefinitely. Note that deleted deployments are not recoverable. - `environment` (Map of String) A map of environment variable names to values. These are specific to a Deployment, and can also be configured on the `vercel_project` resource. +- `files` (Map of String) A map of files to be uploaded for the deployment. This should be provided by a `vercel_project_directory` or `vercel_file` data source. Required if `git_source` is not set +- `git_source` (Attributes) A map with the Git repo information. Required if `files` is not set (see [below for nested schema](#nestedatt--git_source)) - `path_prefix` (String) If specified then the `path_prefix` will be stripped from the start of file paths as they are uploaded to Vercel. If this is omitted, then any leading `../`s will be stripped. - `production` (Boolean) true if the deployment is a production deployment, meaning production aliases will be assigned. - `project_settings` (Attributes) Project settings that will be applied to the deployment. (see [below for nested schema](#nestedatt--project_settings)) @@ -81,6 +82,16 @@ resource "vercel_deployment" "example" { - `id` (String) The ID of this resource. - `url` (String) A unique URL that is automatically generated for a deployment. + +### Nested Schema for `git_source` + +Optional: + +- `ref` (String) Branch or commit hash +- `repo_id` (String) Frontend git repo ID +- `type` (String) Type of git repo, supported values are: github + + ### Nested Schema for `project_settings` diff --git a/vercel/resource_deployment.go b/vercel/resource_deployment.go index 38d847fe..59418320 100644 --- a/vercel/resource_deployment.go +++ b/vercel/resource_deployment.go @@ -79,8 +79,8 @@ Once the build step has completed successfully, a new, immutable deployment will Type: types.BoolType, }, "files": { - Description: "A map of files to be uploaded for the deployment. This should be provided by a `vercel_project_directory` or `vercel_file` data source.", - Required: true, + Description: "A map of files to be uploaded for the deployment. This should be provided by a `vercel_project_directory` or `vercel_file` data source. Required if `git_source` is not set", + Optional: true, PlanModifiers: tfsdk.AttributePlanModifiers{tfsdk.RequiresReplace()}, Type: types.MapType{ ElemType: types.StringType, @@ -89,6 +89,34 @@ Once the build step has completed successfully, a new, immutable deployment will mapItemsMinCount(1), }, }, + "git_source": { + Description: "A map with the Git repo information. Required if `files` is not set", + Optional: true, + PlanModifiers: tfsdk.AttributePlanModifiers{tfsdk.RequiresReplace()}, + Attributes: tfsdk.SingleNestedAttributes(map[string]tfsdk.Attribute{ + "repo_id": { + Required: true, + PlanModifiers: tfsdk.AttributePlanModifiers{tfsdk.RequiresReplace()}, + Type: types.StringType, + Description: "Frontend git repo ID", + }, + "ref": { + Required: true, + PlanModifiers: tfsdk.AttributePlanModifiers{tfsdk.RequiresReplace()}, + Type: types.StringType, + Description: "Branch or commit hash", + }, + "type": { + Required: true, + PlanModifiers: tfsdk.AttributePlanModifiers{tfsdk.RequiresReplace()}, + Type: types.StringType, + Description: "Type of git repo, supported values are: github", + Validators: []tfsdk.AttributeValidator{ + stringOneOf("github", "gitlab", "bitbucket", "custom"), + }, + }, + }), + }, "project_settings": { Description: "Project settings that will be applied to the deployment.", Optional: true, @@ -171,6 +199,14 @@ func (r resourceDeployment) Create(ctx context.Context, req tfsdk.CreateResource ) return } + err := plan.checkMutualyExclusiveAttributes() + if err != nil { + resp.Diagnostics.AddError( + "Error creating deployment", + "Error checking arguments: "+err.Error(), + ) + return + } files, filesBySha, err := plan.getFiles() if err != nil { @@ -192,12 +228,19 @@ func (r resourceDeployment) Create(ctx context.Context, req tfsdk.CreateResource return } + var gitSource *client.GitSource + if plan.GitSource != nil { + gs := plan.GitSource.toRequest() + gitSource = &gs + } + cdr := client.CreateDeploymentRequest{ Files: files, Environment: environment, ProjectID: plan.ProjectID.Value, ProjectSettings: plan.ProjectSettings.toRequest(), Target: target, + GitSource: gitSource, } out, err := r.p.client.CreateDeployment(ctx, cdr, plan.TeamID.Value) diff --git a/vercel/resource_deployment_model.go b/vercel/resource_deployment_model.go index 4f965c98..868093db 100644 --- a/vercel/resource_deployment_model.go +++ b/vercel/resource_deployment_model.go @@ -20,6 +20,13 @@ type ProjectSettings struct { RootDirectory types.String `tfsdk:"root_directory"` } +// GitSource represents the Git integration source for a deployment. +type GitSource struct { + RepoId types.String `tfsdk:"repo_id"` + Ref types.String `tfsdk:"ref"` + Type types.String `tfsdk:"type"` +} + // Deployment represents the terraform state for a deployment resource. type Deployment struct { Domains types.List `tfsdk:"domains"` @@ -33,6 +40,7 @@ type Deployment struct { TeamID types.String `tfsdk:"team_id"` URL types.String `tfsdk:"url"` DeleteOnDestroy types.Bool `tfsdk:"delete_on_destroy"` + GitSource *GitSource `tfsdk:"git_source"` } // setIfNotUnknown is a helper function to set a value in a map if it is not unknown. @@ -46,6 +54,16 @@ func setIfNotUnknown(m map[string]interface{}, v types.String, name string) { } } +// toRequest takes the input of GitSource and converts them into the required +// format for a CreateDeploymentRequest. +func (g *GitSource) toRequest() client.GitSource { + return client.GitSource{ + Ref: g.Ref.Value, + RepoId: g.RepoId.Value, + Type: g.Type.Value, + } +} + // toRequest takes a set of ProjectSettings and converts them into the required // format for a CreateDeploymentRequest. func (p *ProjectSettings) toRequest() map[string]interface{} { @@ -187,5 +205,19 @@ func convertResponseToDeployment(response client.DeploymentResponse, plan Deploy PathPrefix: fillStringNull(plan.PathPrefix), ProjectSettings: plan.ProjectSettings.fillNulls(), DeleteOnDestroy: plan.DeleteOnDestroy, + GitSource: plan.GitSource, + } +} + +// checkMutualyExclusiveAttributes checks whether git_source and files are not set at the same time +func (d *Deployment) checkMutualyExclusiveAttributes() error { + // Error if both are nil + if d.Files != nil && d.GitSource != nil { + return fmt.Errorf("only one of \"files\" or \"git_source\" must be set, not both") + } + // Error if both are set + if d.Files == nil && d.GitSource == nil { + return fmt.Errorf("either \"files\" or \"git_source\" must be set") } + return nil } diff --git a/vercel/resource_deployment_test.go b/vercel/resource_deployment_test.go index 6fd3c6f6..9abd7557 100644 --- a/vercel/resource_deployment_test.go +++ b/vercel/resource_deployment_test.go @@ -248,6 +248,24 @@ func testAccDeployment(t *testing.T, tid string) { }) } +func TestAcc_DeployFromGitSource(t *testing.T) { + projectSuffix := acctest.RandString(16) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: noopDestroyCheck, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDeployFromGitSource(projectSuffix), + Check: resource.ComposeAggregateTestCheckFunc( + testAccDeploymentExists("vercel_deployment.test", ""), + resource.TestCheckResourceAttr("vercel_deployment.test", "production", "true"), + ), + }, + }, + }) +} + func testAccDeploymentConfigWithNoDeployment(projectSuffix string) string { return fmt.Sprintf(` resource "vercel_project" "test" { @@ -327,3 +345,20 @@ resource "vercel_deployment" "test" { path_prefix = "../vercel/example" }`, projectSuffix) } + +func testAccDeployFromGitSource(projectSuffix string) string { + return fmt.Sprintf(` +resource "vercel_project" "test" { + name = "test-acc-deployment-%s" +} + +resource "vercel_deployment" "test" { + project_id = vercel_project.test.id + git_source = { + ref = "main" + repo_id = "452772221" + type = "github" + } + path_prefix = "vercel/example" +}`, projectSuffix) +}