这是indexloc提供的服务,不要输入任何密码
Skip to content

GitSource: added git source deployment #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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
25 changes: 16 additions & 9 deletions client/deployment_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ type DeploymentFile struct {
Sha string `json:"sha"`
Size int `json:"size"`
}
type GitSource struct {
RepoId string `json:"repoId"`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Digging into this, where does the repoId come from?
I can see in the example that you have it as a number. 🤔. I wonder if we need some data source to expose this so it's more usable?

Copy link
Collaborator

@dglsparsons dglsparsons May 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, so repoId looks like it is exposed through a project. I think it could be exposed from both the vercel_project resource and the vercel_project data source.
This would mean you can use it like:

data "vercel_project" "my_project" {
   name = "my-project"
}

resource "vercel_deployment" "example" {
  team_id    = var.vercel_team_id
  project_id = data.vercel_project.my_project.id
  git_source = {
    ref = "main" // or any other branch you fancy. 
    repo_id = data.vercel_project.my_project.git_repository.repo_id
    type = data.vercel_project.my_project.git_repository.type
  }
}

I think this would be more preferable than having to dig out a repoId. What do you think @WCMinor?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another issue I can see here is that Terraform would not know when the vercel_deployment resource needs re-creating. Short of having something like the ref set to a specific commit ID. So this would only act as a one-off deployment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true that you can find it out that way, but I think it's somewhat surprising to see (and results in a magic number). I think if we had the data source, or exposed it via a project it would be more intuitive on how to use the two together to trigger a deployment.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity have you considered putting them in a single repository, behind something like https://turborepo.org/ ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we're actually moving on from a monorepo to a distributed system to allow each team to manage their own code as the teams get bigger

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense! As an update on this issue - I've been having a play around, and I'm coming to the belief that the only thing needed is a ref field on the vercel_deployment resource.

For example

resource "vercel_project" "example" {
   name = "my-test-project" 
   git_repository = {
       type = "github" 
       repo = "acme/example"
   }
}

# Deploy the main branch of acme/example. 
resource "vercel_deployment" "example" {
    project_id = vercel_project.example.id
    ref        = "main" # or a commit ID. 
}

The deployment resource can, itself, look up the relevant repoId / equivalent bitbucket/gitlab parameters, and pass these onto the deployment creation.

You could even use it with a data-source to pull a project by a name:

data "vercel_project" "example" {
    name = "my-test-project" 
}

resource "vercel_deployment" "example" {
    project_id = data.vercel_project.example.id
    ref        = "main" 
}

We already have a hard requirement that for a deployment to be created, a project must already exist. I think using the git-connection this way would work nicely, and also prevents any confusing fields being presented back out of the provider (like repoId or projectUuid or workspaceUuid). What do you think @WCMinor? Would this work for your use-case?

With my playing around I think i'm fairly close to a working version, so if this works for you, I should be able to have a PR up soonish.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great yeah, much easier to understand and to configure.

Funny enough, if you pass a public github repo (not configured in your project) to the deploy API , it works, it deploys a project based on such public repo. That is the only case in which it would make sense to use a standalone git_source parameter. Honestly, I think that functionality is kinda out of the scope of this provider as it is not really documented by Vercel.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. It's probably more a result of accidental API design than anything else 😓

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 {
Expand All @@ -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.
Expand All @@ -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.
Expand Down
13 changes: 12 additions & 1 deletion docs/resources/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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.

<a id="nestedatt--git_source"></a>
### 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


<a id="nestedatt--project_settings"></a>
### Nested Schema for `project_settings`

Expand Down
47 changes: 45 additions & 2 deletions vercel/resource_deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down
32 changes: 32 additions & 0 deletions vercel/resource_deployment_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}

Comment on lines +23 to +29
Copy link
Collaborator

@dglsparsons dglsparsons May 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'd also want to add acceptance tests specifically around creating a resource_deployment with git_source set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

working on these

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// Deployment represents the terraform state for a deployment resource.
type Deployment struct {
Domains types.List `tfsdk:"domains"`
Expand All @@ -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.
Expand All @@ -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{} {
Expand Down Expand Up @@ -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 {
Comment on lines +212 to +213
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can do this as a validation instead, but I'll merge the changes and fix this up retrospectively.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that makes sense

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to clarify, are you merging this or you require more changes?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, i wasn't very clear. I meant i'd take a look at fixing that (either post merge, or by checking out your branch). I've only had a surface level skim so far :)

// 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
}
35 changes: 35 additions & 0 deletions vercel/resource_deployment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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" {
Expand Down Expand Up @@ -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)
}