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

Add ability to create Deployment from git ref #29

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

Merged
merged 3 commits into from
May 11, 2022
Merged
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
4 changes: 4 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ./...
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
73 changes: 64 additions & 9 deletions client/deployment_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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{
Org: project.Link.Org,
Ref: ref,
Repo: project.Link.Repo,
Type: "github",
}, nil
case "gitlab":
return gitSource{
ProjectID: project.Link.ProjectID,
Ref: ref,
Type: "gitlab",
}, nil
case "bitbucket":
return gitSource{
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")
}
}

// 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)
Expand Down
1 change: 1 addition & 0 deletions client/project_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
30 changes: 23 additions & 7 deletions docs/resources/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,41 +38,57 @@ 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
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### 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
Expand Down
27 changes: 21 additions & 6 deletions examples/resources/vercel_deployment/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions vercel/data_source_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
},
Expand All @@ -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()},
},
}),
Expand Down
38 changes: 37 additions & 1 deletion vercel/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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")
}
42 changes: 39 additions & 3 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,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,
Expand Down Expand Up @@ -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.Null && !config.Files.Null {
resp.Diagnostics.AddError(
"Deployment Invalid",
"A Deployment cannot have both `ref` and `files` specified",
)
}
if config.Ref.Null && 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.
Expand All @@ -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",
Expand All @@ -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)
Expand Down
Loading