From e450b92aa7dba78283e26789338f15f31a7d955f Mon Sep 17 00:00:00 2001 From: Douglas Parsons Date: Fri, 22 Apr 2022 17:56:30 +0200 Subject: [PATCH 1/3] Add ability to create Deployment from git ref - Refactor test to use env variables for repositories --- .github/workflows/test.yml | 4 + README.md | 9 +- client/deployment_create.go | 73 ++++++++++++++-- client/project_get.go | 1 + docs/resources/deployment.md | 30 +++++-- .../resources/vercel_deployment/resource.tf | 27 ++++-- vercel/data_source_project.go | 4 +- vercel/provider_test.go | 38 ++++++++- vercel/resource_deployment.go | 42 ++++++++- vercel/resource_deployment_model.go | 38 +++++---- vercel/resource_deployment_test.go | 85 +++++++++++++++++-- vercel/resource_project_domain_test.go | 6 +- vercel/resource_project_test.go | 16 ++-- 13 files changed, 308 insertions(+), 65 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5573dd6d..b7729547 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,5 +67,9 @@ jobs: TF_ACC: "true" VERCEL_API_TOKEN: ${{ secrets.VERCEL_API_TOKEN }} VERCEL_TERRAFORM_TESTING_TEAM: "team_Xd4Fk1OZ0Z1eAvo7nXv3HF5B" + VERCEL_TERRAFORM_TESTING_GITHUB_REPO: "dglsparsons/test" + VERCEL_TERRAFORM_TESTING_GITLAB_REPO: "dglsparsons/test" + VERCEL_TERRAFORM_TESTING_BITBUCKET_REPO: "dglsparsons-test/test" + run: | go test -v -cover ./... diff --git a/README.md b/README.md index 89df7953..05be6e82 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,12 @@ In order to test the provider, you can simply run `task test`. _Note:_ This runs acceptance tests that will create real resources. You should expect that the full acceptance test suite will take some time to run. -The acceptance tests require a `VERCEL_API_TOKEN` (which can be generated [here](https://vercel.com/account/tokens), and -a `VERCEL_TERRAFORM_TESTING_TEAM` (which should be a Vercel team_id where resources can be created and destroyed) -environment variable set. +The acceptance tests require a few environment variables to be set: +* `VERCEL_API_TOKEN` - this can be generated [here](https://vercel.com/account/tokens) +* `VERCEL_TERRAFORM_TESTING_TEAM` - a Vercel team_id where resources can be created and destroyed +* `VERCEL_TERRAFORM_TESTING_GITHUB_REPO` - a github repository in the form 'org/repo' that can be used to trigger deployments +* `VERCEL_TERRAFORM_TESTING_GITLAB_REPO` - a gitlab repository in the form 'namespace/repo' that can be used to trigger deployments +* `VERCEL_TERRAFORM_TESTING_BITBUCKET_REPO` - a bitbucket repository in the form 'project/repo' that can be used to trigger deployments ```sh $ task test diff --git a/client/deployment_create.go b/client/deployment_create.go index 5ac38fc5..d095b477 100644 --- a/client/deployment_create.go +++ b/client/deployment_create.go @@ -21,6 +21,16 @@ type DeploymentFile struct { Size int `json:"size"` } +type gitSource struct { + Type string `json:"type"` + Org string `json:"org,omitempty"` + Repo string `json:"repo,omitempty"` + ProjectID int64 `json:"projectId,omitempty"` + Owner string `json:"owner,omitempty"` + Slug string `json:"slug,omitempty"` + Ref string `json:"ref"` +} + // CreateDeploymentRequest defines the request the Vercel API expects in order to create a deployment. type CreateDeploymentRequest struct { Files []DeploymentFile `json:"files,omitempty"` @@ -35,6 +45,8 @@ type CreateDeploymentRequest struct { Regions []string `json:"regions,omitempty"` Routes []interface{} `json:"routes,omitempty"` Target string `json:"target,omitempty"` + GitSource *gitSource `json:"gitSource,omitempty"` + Ref string `json:"-"` } // DeploymentResponse defines the response the Vercel API returns when a deployment is created or updated. @@ -59,15 +71,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. @@ -136,10 +149,52 @@ func (e MissingFilesError) Error() string { return fmt.Sprintf("%s - %s", e.Code, e.Message) } +func (c *Client) getGitSource(ctx context.Context, projectID, ref, teamID string) (gs gitSource, err error) { + project, err := c.GetProject(ctx, projectID, teamID) + if err != nil { + return gs, fmt.Errorf("error getting project: %w", err) + } + if project.Link == nil { + return gs, fmt.Errorf("unable to deploy project by ref: project has no linked git repository") + } + + switch project.Link.Type { + case "github": + return gitSource{ + Type: "github", + Org: project.Link.Org, + Repo: project.Link.Repo, + Ref: ref, + }, nil + case "gitlab": + return gitSource{ + ProjectID: project.Link.ProjectID, + Type: "gitlab", + Ref: ref, + }, nil + case "bitbucket": + return gitSource{ + Type: "bitbucket", + Ref: ref, + Owner: project.Link.Owner, + Slug: project.Link.Slug, + }, nil + default: + return gs, fmt.Errorf("unable to deploy project by ref: project has no linked git repository") + } +} + // CreateDeployment creates a deployment within Vercel. func (c *Client) CreateDeployment(ctx context.Context, request CreateDeploymentRequest, teamID string) (r DeploymentResponse, err error) { request.Name = request.ProjectID // Name is ignored if project is specified request.Build.Environment = request.Environment // Ensure they are both the same, as project environment variables are + if request.Ref != "" { + gitSource, err := c.getGitSource(ctx, request.ProjectID, request.Ref, teamID) + if err != nil { + return r, err + } + request.GitSource = &gitSource + } url := fmt.Sprintf("%s/v12/now/deployments?skipAutoDetectionConfirmation=1", c.baseURL) if teamID != "" { url = fmt.Sprintf("%s&teamId=%s", url, teamID) diff --git a/client/project_get.go b/client/project_get.go index 26ff4b23..6ac51fd6 100644 --- a/client/project_get.go +++ b/client/project_get.go @@ -59,6 +59,7 @@ type ProjectResponse struct { // gitlab ProjectNamespace string `json:"projectNamespace"` ProjectName string `json:"projectName"` + ProjectID int64 `json:"projectId,string"` } `json:"link"` Name string `json:"name"` OutputDirectory *string `json:"outputDirectory"` diff --git a/docs/resources/deployment.md b/docs/resources/deployment.md index 009d2a36..b7c01dd5 100644 --- a/docs/resources/deployment.md +++ b/docs/resources/deployment.md @@ -38,24 +38,39 @@ Once the build step has completed successfully, a new, immutable deployment will # main.tf # ``` -data "vercel_project_directory" "example" { +data "vercel_project_directory" "files_example" { path = "../ui" } -data "vercel_project" "example" { +data "vercel_project" "files_example" { name = "my-awesome-project" } -resource "vercel_deployment" "example" { - project_id = data.vercel_project.example.id - files = data.vercel_project_directory.example.files - path_prefix = data.vercel_project_directory.example.path +resource "vercel_deployment" "files_example" { + project_id = data.vercel_project.files_example.id + files = data.vercel_project_directory.files_example.files + path_prefix = data.vercel_project_directory.files_example.path production = true environment = { FOO = "bar" } } + +## Or deploying a specific commit or branch +resource "vercel_project" "git_example" { + name = "my-awesome-git-project" + framework = "nextjs" + git_repository = { + type = "github" + repo = "vercel/some-repo" + } +} + +resource "vercel_deployment" "git_example" { + project_id = vercel_project.git_example.id + ref = "d92f10e" # or a git branch +} ``` @@ -63,16 +78,17 @@ 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. - `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)) +- `ref` (String) The branch or commit hash that should be deployed. Note this will only work if the project is configured to use a Git repository. Required if `ref` is not set. - `team_id` (String) The team ID to add the deployment to. ### Read-Only diff --git a/examples/resources/vercel_deployment/resource.tf b/examples/resources/vercel_deployment/resource.tf index 5e3635ed..18439dd9 100644 --- a/examples/resources/vercel_deployment/resource.tf +++ b/examples/resources/vercel_deployment/resource.tf @@ -11,21 +11,36 @@ # main.tf # ``` -data "vercel_project_directory" "example" { +data "vercel_project_directory" "files_example" { path = "../ui" } -data "vercel_project" "example" { +data "vercel_project" "files_example" { name = "my-awesome-project" } -resource "vercel_deployment" "example" { - project_id = data.vercel_project.example.id - files = data.vercel_project_directory.example.files - path_prefix = data.vercel_project_directory.example.path +resource "vercel_deployment" "files_example" { + project_id = data.vercel_project.files_example.id + files = data.vercel_project_directory.files_example.files + path_prefix = data.vercel_project_directory.files_example.path production = true environment = { FOO = "bar" } } + +## Or deploying a specific commit or branch +resource "vercel_project" "git_example" { + name = "my-awesome-git-project" + framework = "nextjs" + git_repository = { + type = "github" + repo = "vercel/some-repo" + } +} + +resource "vercel_deployment" "git_example" { + project_id = vercel_project.git_example.id + ref = "d92f10e" # or a git branch +} diff --git a/vercel/data_source_project.go b/vercel/data_source_project.go index f9a2d6d0..38e535da 100644 --- a/vercel/data_source_project.go +++ b/vercel/data_source_project.go @@ -98,7 +98,7 @@ For more detailed information, please see the [Vercel documentation](https://ver "type": { Description: "The git provider of the repository. Must be either `github`, `gitlab`, or `bitbucket`.", Type: types.StringType, - Required: true, + Computed: true, Validators: []tfsdk.AttributeValidator{ stringOneOf("github", "gitlab", "bitbucket"), }, @@ -107,7 +107,7 @@ For more detailed information, please see the [Vercel documentation](https://ver "repo": { Description: "The name of the git repository. For example: `vercel/next.js`.", Type: types.StringType, - Required: true, + Computed: true, PlanModifiers: tfsdk.AttributePlanModifiers{tfsdk.RequiresReplace()}, }, }), diff --git a/vercel/provider_test.go b/vercel/provider_test.go index e24e9f6a..83bacc23 100644 --- a/vercel/provider_test.go +++ b/vercel/provider_test.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/tfsdk" "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/vercel/terraform-provider-vercel/client" "github.com/vercel/terraform-provider-vercel/vercel" ) @@ -19,7 +20,42 @@ func testAccPreCheck(t *testing.T) { if v := os.Getenv("VERCEL_API_TOKEN"); v == "" { t.Fatal("VERCEL_API_TOKEN must be set for acceptance tests") } - if v := os.Getenv("VERCEL_TERRAFORM_TESTING_TEAM"); v == "" { + if v := testTeam(); v == "" { t.Fatal("VERCEL_TERRAFORM_TESTING_TEAM must be set for acceptance tests against a specific team") } + if v := testGithubRepo(); v == "" { + t.Fatal("VERCEL_TERRAFORM_TESTING_GITHUB_REPO must be set for acceptance tests against a github repository") + } + if v := testGitlabRepo(); v == "" { + t.Fatal("VERCEL_TERRAFORM_TESTING_GITLAB_REPO must be set for acceptance tests against a gitlab repository") + } + if v := testBitbucketRepo(); v == "" { + t.Fatal("VERCEL_TERRAFORM_TESTING_BITBUCKET_REPO must be set for acceptance tests against a bitbucket repository") + } +} + +var tc *client.Client + +func testClient() *client.Client { + if tc == nil { + tc = client.New(os.Getenv("VERCEL_API_TOKEN")) + } + + return tc +} + +func testGithubRepo() string { + return os.Getenv("VERCEL_TERRAFORM_TESTING_GITHUB_REPO") +} + +func testGitlabRepo() string { + return os.Getenv("VERCEL_TERRAFORM_TESTING_GITLAB_REPO") +} + +func testBitbucketRepo() string { + return os.Getenv("VERCEL_TERRAFORM_TESTING_BITBUCKET_REPO") +} + +func testTeam() string { + return os.Getenv("VERCEL_TERRAFORM_TESTING_TEAM") } diff --git a/vercel/resource_deployment.go b/vercel/resource_deployment.go index 38d847fe..07f96b8f 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,12 @@ Once the build step has completed successfully, a new, immutable deployment will mapItemsMinCount(1), }, }, + "ref": { + Description: "The branch or commit hash that should be deployed. Note this will only work if the project is configured to use a Git repository. Required if `ref` is not set.", + Optional: true, + PlanModifiers: tfsdk.AttributePlanModifiers{tfsdk.RequiresReplace()}, + Type: types.StringType, + }, "project_settings": { Description: "Project settings that will be applied to the deployment.", Optional: true, @@ -149,6 +155,29 @@ type resourceDeployment struct { p provider } +// ValidateConfig allows additional validation (specifically cross-field validation) to be added. +func (r resourceDeployment) ValidateConfig(ctx context.Context, req tfsdk.ValidateResourceConfigRequest, resp *tfsdk.ValidateResourceConfigResponse) { + var config Deployment + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + if config.Ref.Value != "" && !config.Files.Null { + resp.Diagnostics.AddError( + "Deployment Invalid", + "A Deployment cannot have both `ref` and `files` specified", + ) + } + if config.Ref.Value == "" && config.Files.Null { + resp.Diagnostics.AddError( + "Deployment Invalid", + "A Deployment must have either `ref` or `files` specified", + ) + } +} + // Create will create a deployment within Vercel. This is done by first attempting to trigger a deployment, seeing what // files are required, uploading those files, and then attempting to create a deployment again. // This is called automatically by the provider when a new resource should be created. @@ -172,7 +201,13 @@ func (r resourceDeployment) Create(ctx context.Context, req tfsdk.CreateResource return } - files, filesBySha, err := plan.getFiles() + var unparsedFiles map[string]string + diags = plan.Files.ElementsAs(ctx, &unparsedFiles, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + files, filesBySha, err := getFiles(unparsedFiles, plan.PathPrefix) if err != nil { resp.Diagnostics.AddError( "Error creating deployment", @@ -198,6 +233,7 @@ func (r resourceDeployment) Create(ctx context.Context, req tfsdk.CreateResource ProjectID: plan.ProjectID.Value, ProjectSettings: plan.ProjectSettings.toRequest(), Target: target, + Ref: plan.Ref.Value, } 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..ad5b3c86 100644 --- a/vercel/resource_deployment_model.go +++ b/vercel/resource_deployment_model.go @@ -22,17 +22,18 @@ type ProjectSettings struct { // Deployment represents the terraform state for a deployment resource. type Deployment struct { - Domains types.List `tfsdk:"domains"` - Environment types.Map `tfsdk:"environment"` - Files map[string]string `tfsdk:"files"` - ID types.String `tfsdk:"id"` - Production types.Bool `tfsdk:"production"` - ProjectID types.String `tfsdk:"project_id"` - PathPrefix types.String `tfsdk:"path_prefix"` - ProjectSettings *ProjectSettings `tfsdk:"project_settings"` - TeamID types.String `tfsdk:"team_id"` - URL types.String `tfsdk:"url"` - DeleteOnDestroy types.Bool `tfsdk:"delete_on_destroy"` + Domains types.List `tfsdk:"domains"` + Environment types.Map `tfsdk:"environment"` + Files types.Map `tfsdk:"files"` + ID types.String `tfsdk:"id"` + Production types.Bool `tfsdk:"production"` + ProjectID types.String `tfsdk:"project_id"` + PathPrefix types.String `tfsdk:"path_prefix"` + ProjectSettings *ProjectSettings `tfsdk:"project_settings"` + TeamID types.String `tfsdk:"team_id"` + URL types.String `tfsdk:"url"` + DeleteOnDestroy types.Bool `tfsdk:"delete_on_destroy"` + Ref types.String `tfsdk:"ref"` } // setIfNotUnknown is a helper function to set a value in a map if it is not unknown. @@ -102,10 +103,11 @@ func (p *ProjectSettings) fillNulls() *ProjectSettings { // getFiles is a helper for turning the terraform deployment state into a set of client.DeploymentFile // structs, ready to hit the API with. It also returns a map of files by sha, which is used to quickly // look up any missing SHAs from the create deployment resposnse. -func (d *Deployment) getFiles() ([]client.DeploymentFile, map[string]client.DeploymentFile, error) { +func getFiles(unparsedFiles map[string]string, pathPrefix types.String) ([]client.DeploymentFile, map[string]client.DeploymentFile, error) { var files []client.DeploymentFile filesBySha := map[string]client.DeploymentFile{} - for filename, rawSizeAndSha := range d.Files { + + for filename, rawSizeAndSha := range unparsedFiles { sizeSha := strings.Split(rawSizeAndSha, "~") if len(sizeSha) != 2 { return nil, nil, fmt.Errorf("expected file to have format `filename: size~sha`, but could not parse") @@ -117,12 +119,12 @@ func (d *Deployment) getFiles() ([]client.DeploymentFile, map[string]client.Depl sha := sizeSha[1] untrimmedFilename := filename - if d.PathPrefix.Unknown || d.PathPrefix.Null { + if pathPrefix.Unknown || pathPrefix.Null { for strings.HasPrefix(filename, "../") { filename = strings.TrimPrefix(filename, "../") } } else { - filename = strings.TrimPrefix(filename, d.PathPrefix.Value) + filename = strings.TrimPrefix(filename, pathPrefix.Value) } file := client.DeploymentFile{ File: filename, @@ -172,6 +174,11 @@ func convertResponseToDeployment(response client.DeploymentResponse, plan Deploy plan.Environment.Null = true } + if plan.Files.Unknown || plan.Files.Null { + plan.Files.Unknown = false + plan.Files.Null = true + } + return Deployment{ Domains: types.List{ ElemType: types.StringType, @@ -187,5 +194,6 @@ func convertResponseToDeployment(response client.DeploymentResponse, plan Deploy PathPrefix: fillStringNull(plan.PathPrefix), ProjectSettings: plan.ProjectSettings.fillNulls(), DeleteOnDestroy: plan.DeleteOnDestroy, + Ref: types.String{Value: response.GitSource.Ref, Unknown: false, Null: response.GitSource.Ref == ""}, } } diff --git a/vercel/resource_deployment_test.go b/vercel/resource_deployment_test.go index 6fd3c6f6..8fe4fd96 100644 --- a/vercel/resource_deployment_test.go +++ b/vercel/resource_deployment_test.go @@ -25,8 +25,7 @@ func testAccDeploymentExists(n, teamID string) resource.TestCheckFunc { return fmt.Errorf("no DeploymentID is set") } - c := client.New(os.Getenv("VERCEL_API_TOKEN")) - _, err := c.GetDeployment(context.TODO(), rs.Primary.ID, teamID) + _, err := testClient().GetDeployment(context.TODO(), rs.Primary.ID, teamID) return err } } @@ -50,8 +49,7 @@ func testAccEnvironmentSet(n, teamID string, envs ...string) resource.TestCheckF return fmt.Errorf("no DeploymentID is set") } - c := client.New(os.Getenv("VERCEL_API_TOKEN")) - dpl, err := c.GetDeployment(context.TODO(), rs.Primary.ID, teamID) + dpl, err := testClient().GetDeployment(context.TODO(), rs.Primary.ID, teamID) if err != nil { return err } @@ -178,8 +176,7 @@ func TestAcc_DeploymentWithDeleteOnDestroy(t *testing.T) { } testDeploymentGone := func() resource.TestCheckFunc { return func(*terraform.State) error { - c := client.New(os.Getenv("VERCEL_API_TOKEN")) - _, err := c.GetDeployment(context.TODO(), deploymentId, "") + _, err := testClient().GetDeployment(context.TODO(), deploymentId, "") if err == nil { return fmt.Errorf("expected not_found error, but got no error") } @@ -248,6 +245,38 @@ func testAccDeployment(t *testing.T, tid string) { }) } +func TestAcc_DeploymentWithGitSource(t *testing.T) { + tests := map[string]string{ + "personal scope": "", + "team scope": os.Getenv("VERCEL_TERRAFORM_TESTING_TEAM"), + } + + for name, teamID := range tests { + t.Run(name, func(t *testing.T) { + extraConfig := "" + projectSuffix := acctest.RandString(16) + if teamID != "" { + extraConfig = fmt.Sprintf(`team_id = "%s"`, teamID) + } + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + CheckDestroy: noopDestroyCheck, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccDeployFromGitSource(projectSuffix, extraConfig), + Check: resource.ComposeAggregateTestCheckFunc( + testAccDeploymentExists("vercel_deployment.bitbucket", teamID), + testAccDeploymentExists("vercel_deployment.gitlab", teamID), + testAccDeploymentExists("vercel_deployment.github", teamID), + ), + }, + }, + }) + }) + } +} + func testAccDeploymentConfigWithNoDeployment(projectSuffix string) string { return fmt.Sprintf(` resource "vercel_project" "test" { @@ -327,3 +356,47 @@ resource "vercel_deployment" "test" { path_prefix = "../vercel/example" }`, projectSuffix) } + +func testAccDeployFromGitSource(projectSuffix, extras string) string { + return fmt.Sprintf(` +resource "vercel_project" "github" { + name = "test-acc-deployment-%[1]s-github" + git_repository = { + type = "github" + repo = "%[2]s" + } + %[5]s +} +resource "vercel_project" "gitlab" { + name = "test-acc-deployment-%[1]s-gitlab" + git_repository = { + type = "gitlab" + repo = "%[3]s" + } + %[5]s +} +resource "vercel_project" "bitbucket" { + name = "test-acc-deployment-%[1]s-bitbucket" + git_repository = { + type = "bitbucket" + repo = "%[4]s" + } + %[5]s +} +resource "vercel_deployment" "github" { + project_id = vercel_project.github.id + ref = "main" + %[5]s +} +resource "vercel_deployment" "gitlab" { + project_id = vercel_project.gitlab.id + ref = "main" + %[5]s +} +resource "vercel_deployment" "bitbucket" { + project_id = vercel_project.bitbucket.id + ref = "main" + %[5]s +} +`, projectSuffix, testGithubRepo(), testGitlabRepo(), testBitbucketRepo(), extras) +} diff --git a/vercel/resource_project_domain_test.go b/vercel/resource_project_domain_test.go index 98364d99..7ea3ab61 100644 --- a/vercel/resource_project_domain_test.go +++ b/vercel/resource_project_domain_test.go @@ -34,8 +34,7 @@ func testAccProjectDomainExists(n, teamID, domain string) resource.TestCheckFunc return fmt.Errorf("no projectID is set") } - c := client.New(os.Getenv("VERCEL_API_TOKEN")) - _, err := c.GetProjectDomain(context.TODO(), rs.Primary.ID, domain, teamID) + _, err := testClient().GetProjectDomain(context.TODO(), rs.Primary.ID, domain, teamID) return err } } @@ -51,8 +50,7 @@ func testAccProjectDomainDestroy(n, teamID, domain string) resource.TestCheckFun return fmt.Errorf("no projectID is set") } - c := client.New(os.Getenv("VERCEL_API_TOKEN")) - _, err := c.GetProjectDomain(context.TODO(), rs.Primary.ID, domain, teamID) + _, err := testClient().GetProjectDomain(context.TODO(), rs.Primary.ID, domain, teamID) var apiErr client.APIError if err == nil { return fmt.Errorf("Found project domain but expected it to have been deleted") diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go index 1b436ea7..ca8d8025 100644 --- a/vercel/resource_project_test.go +++ b/vercel/resource_project_test.go @@ -60,7 +60,7 @@ func TestAcc_ProjectWithGitRepository(t *testing.T) { Check: resource.ComposeAggregateTestCheckFunc( testAccProjectExists("vercel_project.test_git", ""), resource.TestCheckResourceAttr("vercel_project.test_git", "git_repository.type", "github"), - resource.TestCheckResourceAttr("vercel_project.test_git", "git_repository.repo", "dglsparsons/test"), + resource.TestCheckResourceAttr("vercel_project.test_git", "git_repository.repo", testGithubRepo()), resource.TestCheckTypeSetElemNestedAttrs("vercel_project.test_git", "environment.*", map[string]string{ "key": "foo", "value": "bar", @@ -117,8 +117,7 @@ func testAccProjectExists(n, teamID string) resource.TestCheckFunc { return fmt.Errorf("no projectID is set") } - c := client.New(os.Getenv("VERCEL_API_TOKEN")) - _, err := c.GetProject(context.TODO(), rs.Primary.ID, teamID) + _, err := testClient().GetProject(context.TODO(), rs.Primary.ID, teamID) return err } } @@ -134,8 +133,7 @@ func testAccProjectDestroy(n, teamID string) resource.TestCheckFunc { return fmt.Errorf("no projectID is set") } - c := client.New(os.Getenv("VERCEL_API_TOKEN")) - _, err := c.GetProject(context.TODO(), rs.Primary.ID, teamID) + _, err := testClient().GetProject(context.TODO(), rs.Primary.ID, teamID) var apiErr client.APIError if err == nil { @@ -259,7 +257,7 @@ resource "vercel_project" "test_git" { name = "test-acc-two-%s" git_repository = { type = "github" - repo = "dglsparsons/test" + repo = "%s" } environment = [ { @@ -270,7 +268,7 @@ resource "vercel_project" "test_git" { } ] } - `, projectSuffix) + `, projectSuffix, testGithubRepo()) } func testAccProjectConfigWithGitRepoUpdated(projectSuffix string) string { @@ -279,7 +277,7 @@ resource "vercel_project" "test_git" { name = "test-acc-two-%s" git_repository = { type = "github" - repo = "dglsparsons/test" + repo = "%s" } environment = [ { @@ -290,7 +288,7 @@ resource "vercel_project" "test_git" { } ] } - `, projectSuffix) + `, projectSuffix, testGithubRepo()) } func testAccProjectConfig(projectSuffix, extra string) string { From 0588965ed6d7f2bcd3f9182acf09b5fec677cced Mon Sep 17 00:00:00 2001 From: Douglas Parsons Date: Tue, 10 May 2022 17:18:45 +0100 Subject: [PATCH 2/3] Amend Deployment resource validation for `ref; --- vercel/resource_deployment.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vercel/resource_deployment.go b/vercel/resource_deployment.go index 07f96b8f..1886a20d 100644 --- a/vercel/resource_deployment.go +++ b/vercel/resource_deployment.go @@ -164,13 +164,13 @@ func (r resourceDeployment) ValidateConfig(ctx context.Context, req tfsdk.Valida return } - if config.Ref.Value != "" && !config.Files.Null { + if !config.Ref.Null && !config.Files.Null { resp.Diagnostics.AddError( "Deployment Invalid", "A Deployment cannot have both `ref` and `files` specified", ) } - if config.Ref.Value == "" && config.Files.Null { + if config.Ref.Null && config.Files.Null { resp.Diagnostics.AddError( "Deployment Invalid", "A Deployment must have either `ref` or `files` specified", From 1749cab8bc416a511fd6e4122676c19729e48ca9 Mon Sep 17 00:00:00 2001 From: Douglas Parsons Date: Wed, 11 May 2022 16:01:17 +0100 Subject: [PATCH 3/3] Minor tweaks following feedback --- README.md | 6 +++--- client/deployment_create.go | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 05be6e82..8b5e63cd 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ _Note:_ This runs acceptance tests that will create real resources. You should e The acceptance tests require a few environment variables to be set: * `VERCEL_API_TOKEN` - this can be generated [here](https://vercel.com/account/tokens) * `VERCEL_TERRAFORM_TESTING_TEAM` - a Vercel team_id where resources can be created and destroyed -* `VERCEL_TERRAFORM_TESTING_GITHUB_REPO` - a github repository in the form 'org/repo' that can be used to trigger deployments -* `VERCEL_TERRAFORM_TESTING_GITLAB_REPO` - a gitlab repository in the form 'namespace/repo' that can be used to trigger deployments -* `VERCEL_TERRAFORM_TESTING_BITBUCKET_REPO` - a bitbucket repository in the form 'project/repo' that can be used to trigger deployments +* `VERCEL_TERRAFORM_TESTING_GITHUB_REPO` - a GitHub repository in the form 'org/repo' that can be used to trigger deployments +* `VERCEL_TERRAFORM_TESTING_GITLAB_REPO` - a GitLab repository in the form 'namespace/repo' that can be used to trigger deployments +* `VERCEL_TERRAFORM_TESTING_BITBUCKET_REPO` - a Bitbucket repository in the form 'project/repo' that can be used to trigger deployments ```sh $ task test diff --git a/client/deployment_create.go b/client/deployment_create.go index d095b477..143e913b 100644 --- a/client/deployment_create.go +++ b/client/deployment_create.go @@ -161,23 +161,23 @@ func (c *Client) getGitSource(ctx context.Context, projectID, ref, teamID string switch project.Link.Type { case "github": return gitSource{ - Type: "github", Org: project.Link.Org, - Repo: project.Link.Repo, Ref: ref, + Repo: project.Link.Repo, + Type: "github", }, nil case "gitlab": return gitSource{ ProjectID: project.Link.ProjectID, - Type: "gitlab", Ref: ref, + Type: "gitlab", }, nil case "bitbucket": return gitSource{ - Type: "bitbucket", - Ref: ref, Owner: project.Link.Owner, + Ref: ref, Slug: project.Link.Slug, + Type: "bitbucket", }, nil default: return gs, fmt.Errorf("unable to deploy project by ref: project has no linked git repository")