Skip to content

Commit 52e7c22

Browse files
authored
Add support for custom enviroments (#246)
* Support Custom Environments * Support environment variable targets for custom environments * Support project domains * Revert "Support environment variable targets for custom environments" This reverts commit e9fa7dc. * Fleshing out environment stuff * Hopefully fix everything * Fix project resource * Missed a schema change on the project data source * Generate docs...
1 parent 5151989 commit 52e7c22

28 files changed

+1688
-301
lines changed

client/custom_environment.go

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/hashicorp/terraform-plugin-log/tflog"
8+
)
9+
10+
type BranchMatcher struct {
11+
Pattern string `json:"pattern"`
12+
Type string `json:"type"`
13+
}
14+
15+
type CreateCustomEnvironmentRequest struct {
16+
TeamID string `json:"-"`
17+
ProjectID string `json:"-"`
18+
Slug string `json:"slug"`
19+
Description string `json:"description"`
20+
BranchMatcher *BranchMatcher `json:"branchMatcher,omitempty"`
21+
}
22+
23+
type CustomEnvironmentResponse struct {
24+
ID string `json:"id"`
25+
Description string `json:"description"`
26+
Slug string `json:"slug"`
27+
BranchMatcher *BranchMatcher `json:"branchMatcher"`
28+
TeamID string `json:"-"`
29+
ProjectID string `json:"-"`
30+
}
31+
32+
func (c *Client) CreateCustomEnvironment(ctx context.Context, request CreateCustomEnvironmentRequest) (res CustomEnvironmentResponse, err error) {
33+
url := fmt.Sprintf("%s/v1/projects/%s/custom-environments", c.baseURL, request.ProjectID)
34+
if c.teamID(request.TeamID) != "" {
35+
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID))
36+
}
37+
payload := string(mustMarshal(request))
38+
tflog.Info(ctx, "creating custom environment", map[string]interface{}{
39+
"url": url,
40+
"payload": payload,
41+
})
42+
err = c.doRequest(clientRequest{
43+
ctx: ctx,
44+
method: "POST",
45+
url: url,
46+
body: payload,
47+
}, &res)
48+
if err != nil {
49+
return res, err
50+
}
51+
res.TeamID = c.teamID(request.TeamID)
52+
res.ProjectID = request.ProjectID
53+
return res, nil
54+
}
55+
56+
type GetCustomEnvironmentRequest struct {
57+
TeamID string `json:"-"`
58+
ProjectID string `json:"-"`
59+
Slug string `json:"-"`
60+
}
61+
62+
func (c *Client) GetCustomEnvironment(ctx context.Context, request GetCustomEnvironmentRequest) (res CustomEnvironmentResponse, err error) {
63+
url := fmt.Sprintf("%s/v1/projects/%s/custom-environments/%s", c.baseURL, request.ProjectID, request.Slug)
64+
if c.teamID(request.TeamID) != "" {
65+
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID))
66+
}
67+
tflog.Info(ctx, "getting custom environment", map[string]interface{}{
68+
"url": url,
69+
})
70+
err = c.doRequest(clientRequest{
71+
ctx: ctx,
72+
method: "GET",
73+
url: url,
74+
}, &res)
75+
if err != nil {
76+
return res, err
77+
}
78+
res.TeamID = c.teamID(request.TeamID)
79+
res.ProjectID = request.ProjectID
80+
return res, nil
81+
82+
}
83+
84+
type UpdateCustomEnvironmentRequest struct {
85+
TeamID string `json:"-"`
86+
ProjectID string `json:"-"`
87+
OldSlug string `json:"-"` // Needed to get the right URL
88+
Slug string `json:"slug"`
89+
Description string `json:"description"`
90+
BranchMatcher *BranchMatcher `json:"branchMatcher"`
91+
}
92+
93+
func (c *Client) UpdateCustomEnvironment(ctx context.Context, request UpdateCustomEnvironmentRequest) (res CustomEnvironmentResponse, err error) {
94+
url := fmt.Sprintf("%s/v1/projects/%s/custom-environments/%s", c.baseURL, request.ProjectID, request.OldSlug)
95+
if c.teamID(request.TeamID) != "" {
96+
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID))
97+
}
98+
payload := string(mustMarshal(request))
99+
tflog.Info(ctx, "updating custom environment", map[string]interface{}{
100+
"url": url,
101+
"payload": payload,
102+
})
103+
err = c.doRequest(clientRequest{
104+
ctx: ctx,
105+
method: "PATCH",
106+
url: url,
107+
body: payload,
108+
}, &res)
109+
if err != nil {
110+
return res, err
111+
}
112+
res.TeamID = c.teamID(request.TeamID)
113+
res.ProjectID = request.ProjectID
114+
return res, nil
115+
}
116+
117+
type DeleteCustomEnvironmentRequest struct {
118+
TeamID string `json:"-"`
119+
ProjectID string `json:"-"`
120+
Slug string `json:"-"`
121+
}
122+
123+
func (c *Client) DeleteCustomEnvironment(ctx context.Context, request DeleteCustomEnvironmentRequest) error {
124+
url := fmt.Sprintf("%s/v1/projects/%s/custom-environments/%s", c.baseURL, request.ProjectID, request.Slug)
125+
if c.teamID(request.TeamID) != "" {
126+
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID))
127+
}
128+
tflog.Info(ctx, "deleting custom environment", map[string]interface{}{
129+
"url": url,
130+
})
131+
err := c.doRequest(clientRequest{
132+
ctx: ctx,
133+
method: "DELETE",
134+
url: url,
135+
body: "{ \"deleteUnassignedEnvironmentVariables\": true }",
136+
}, nil)
137+
return err
138+
}

client/environment_variable.go

+26-16
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import (
1010
// CreateEnvironmentVariableRequest defines the information that needs to be passed to Vercel in order to
1111
// create an environment variable.
1212
type EnvironmentVariableRequest struct {
13-
Key string `json:"key"`
14-
Value string `json:"value"`
15-
Target []string `json:"target"`
16-
GitBranch *string `json:"gitBranch,omitempty"`
17-
Type string `json:"type"`
18-
Comment string `json:"comment"`
13+
Key string `json:"key"`
14+
Value string `json:"value"`
15+
Target []string `json:"target,omitempty"`
16+
CustomEnvironmentIDs []string `json:"customEnvironmentIds,omitempty"`
17+
GitBranch *string `json:"gitBranch,omitempty"`
18+
Type string `json:"type"`
19+
Comment string `json:"comment"`
1920
}
2021

2122
type CreateEnvironmentVariableRequest struct {
@@ -58,7 +59,7 @@ func (c *Client) CreateEnvironmentVariable(ctx context.Context, request CreateEn
5859
}
5960
}
6061
if err != nil {
61-
return e, err
62+
return e, fmt.Errorf("%w - %s", err, payload)
6263
}
6364
// The API response returns an encrypted environment variable, but we want to return the decrypted version.
6465
e.Value = request.EnvironmentVariable.Value
@@ -127,6 +128,14 @@ type CreateEnvironmentVariablesResponse struct {
127128
}
128129

129130
func (c *Client) CreateEnvironmentVariables(ctx context.Context, request CreateEnvironmentVariablesRequest) ([]EnvironmentVariable, error) {
131+
if len(request.EnvironmentVariables) == 1 {
132+
env, err := c.CreateEnvironmentVariable(ctx, CreateEnvironmentVariableRequest{
133+
EnvironmentVariable: request.EnvironmentVariables[0],
134+
ProjectID: request.ProjectID,
135+
TeamID: request.TeamID,
136+
})
137+
return []EnvironmentVariable{env}, err
138+
}
130139
url := fmt.Sprintf("%s/v10/projects/%s/env", c.baseURL, request.ProjectID)
131140
if c.teamID(request.TeamID) != "" {
132141
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID))
@@ -145,7 +154,7 @@ func (c *Client) CreateEnvironmentVariables(ctx context.Context, request CreateE
145154
body: payload,
146155
}, &response)
147156
if err != nil {
148-
return nil, err
157+
return nil, fmt.Errorf("%w - %s", err, payload)
149158
}
150159

151160
if len(response.Failed) > 0 {
@@ -176,14 +185,15 @@ func (c *Client) CreateEnvironmentVariables(ctx context.Context, request CreateE
176185
// UpdateEnvironmentVariableRequest defines the information that needs to be passed to Vercel in order to
177186
// update an environment variable.
178187
type UpdateEnvironmentVariableRequest struct {
179-
Value string `json:"value"`
180-
Target []string `json:"target"`
181-
GitBranch *string `json:"gitBranch,omitempty"`
182-
Type string `json:"type"`
183-
Comment string `json:"comment"`
184-
ProjectID string `json:"-"`
185-
TeamID string `json:"-"`
186-
EnvID string `json:"-"`
188+
Value string `json:"value"`
189+
Target []string `json:"target"`
190+
CustomEnvironmentIDs []string `json:"customEnvironmentIds,omitempty"`
191+
GitBranch *string `json:"gitBranch,omitempty"`
192+
Type string `json:"type"`
193+
Comment string `json:"comment"`
194+
ProjectID string `json:"-"`
195+
TeamID string `json:"-"`
196+
EnvID string `json:"-"`
187197
}
188198

189199
// UpdateEnvironmentVariable will update an existing environment variable to the latest information.

client/project.go

+11-10
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,16 @@ type OIDCTokenConfig struct {
2323
// EnvironmentVariable defines the information Vercel requires and surfaces about an environment variable
2424
// that is associated with a project.
2525
type EnvironmentVariable struct {
26-
Key string `json:"key"`
27-
Value string `json:"value"`
28-
Target []string `json:"target"`
29-
GitBranch *string `json:"gitBranch,omitempty"`
30-
Type string `json:"type"`
31-
ID string `json:"id,omitempty"`
32-
TeamID string `json:"-"`
33-
Comment string `json:"comment"`
34-
Decrypted bool `json:"decrypted"`
26+
Key string `json:"key"`
27+
Value string `json:"value"`
28+
Target []string `json:"target"`
29+
CustomEnvironmentIDs []string `json:"customEnvironmentIds"`
30+
GitBranch *string `json:"gitBranch,omitempty"`
31+
Type string `json:"type"`
32+
ID string `json:"id,omitempty"`
33+
TeamID string `json:"-"`
34+
Comment string `json:"comment"`
35+
Decrypted bool `json:"decrypted"`
3536
}
3637

3738
type DeploymentExpiration struct {
@@ -46,7 +47,7 @@ type CreateProjectRequest struct {
4647
BuildCommand *string `json:"buildCommand"`
4748
CommandForIgnoringBuildStep *string `json:"commandForIgnoringBuildStep,omitempty"`
4849
DevCommand *string `json:"devCommand"`
49-
EnvironmentVariables []EnvironmentVariable `json:"environmentVariables"`
50+
EnvironmentVariables []EnvironmentVariable `json:"environmentVariables,omitempty"`
5051
Framework *string `json:"framework"`
5152
GitRepository *GitRepository `json:"gitRepository,omitempty"`
5253
InstallCommand *string `json:"installCommand"`

client/project_domain.go

+16-13
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ import (
1212
// used to assign a domain name to any production deployments, but can also be used to configure
1313
// redirects, or to give specific git branches a domain name.
1414
type CreateProjectDomainRequest struct {
15-
Name string `json:"name"`
16-
GitBranch string `json:"gitBranch,omitempty"`
17-
Redirect string `json:"redirect,omitempty"`
18-
RedirectStatusCode int64 `json:"redirectStatusCode,omitempty"`
15+
Name string `json:"name"`
16+
GitBranch string `json:"gitBranch,omitempty"`
17+
CustomEnvironmentID string `json:"customEnvironmentId,omitempty"`
18+
Redirect string `json:"redirect,omitempty"`
19+
RedirectStatusCode int64 `json:"redirectStatusCode,omitempty"`
1920
}
2021

2122
// CreateProjectDomain creates a project domain within Vercel.
@@ -61,12 +62,13 @@ func (c *Client) DeleteProjectDomain(ctx context.Context, projectID, domain, tea
6162
// ProjectDomainResponse defines the information that Vercel exposes about a domain that is
6263
// associated with a vercel project.
6364
type ProjectDomainResponse struct {
64-
Name string `json:"name"`
65-
ProjectID string `json:"projectId"`
66-
TeamID string `json:"-"`
67-
Redirect *string `json:"redirect"`
68-
RedirectStatusCode *int64 `json:"redirectStatusCode"`
69-
GitBranch *string `json:"gitBranch"`
65+
Name string `json:"name"`
66+
ProjectID string `json:"projectId"`
67+
TeamID string `json:"-"`
68+
Redirect *string `json:"redirect"`
69+
RedirectStatusCode *int64 `json:"redirectStatusCode"`
70+
GitBranch *string `json:"gitBranch"`
71+
CustomEnvironmentID *string `json:"customEnvironmentId"`
7072
}
7173

7274
// GetProjectDomain retrieves information about a project domain from Vercel.
@@ -91,9 +93,10 @@ func (c *Client) GetProjectDomain(ctx context.Context, projectID, domain, teamID
9193

9294
// UpdateProjectDomainRequest defines the information necessary to update a project domain.
9395
type UpdateProjectDomainRequest struct {
94-
GitBranch *string `json:"gitBranch"`
95-
Redirect *string `json:"redirect"`
96-
RedirectStatusCode *int64 `json:"redirectStatusCode"`
96+
GitBranch *string `json:"gitBranch"`
97+
CustomEnvironmentID *string `json:"customEnvironmentId,omitempty"`
98+
Redirect *string `json:"redirect"`
99+
RedirectStatusCode *int64 `json:"redirectStatusCode"`
97100
}
98101

99102
// UpdateProjectDomain updates an existing project domain within Vercel.

client/request.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,11 @@ func (c *Client) _doRequest(req *http.Request, v interface{}, errorOnNoContent b
126126
}{
127127
Error: &errorResponse,
128128
})
129+
if errorResponse.Code == "" && errorResponse.Message == "" {
130+
return fmt.Errorf("error performing API request: %d %s", resp.StatusCode, string(responseBody))
131+
}
129132
if err != nil {
130-
return fmt.Errorf("error unmarshaling response for status code %d: %w", resp.StatusCode, err)
133+
return fmt.Errorf("error unmarshaling response for status code %d: %w: %s", resp.StatusCode, err, string(responseBody))
131134
}
132135
errorResponse.StatusCode = resp.StatusCode
133136
errorResponse.RawMessage = responseBody
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "vercel_custom_environment Data Source - terraform-provider-vercel"
4+
subcategory: ""
5+
description: |-
6+
Provides information about an existing CustomEnvironment resource.
7+
An CustomEnvironment allows a vercel_deployment to be accessed through a different URL.
8+
---
9+
10+
# vercel_custom_environment (Data Source)
11+
12+
Provides information about an existing CustomEnvironment resource.
13+
14+
An CustomEnvironment allows a `vercel_deployment` to be accessed through a different URL.
15+
16+
## Example Usage
17+
18+
```terraform
19+
data "vercel_project" "example" {
20+
name = "example-project-with-custom-env"
21+
}
22+
23+
data "vercel_custom_environment" "example" {
24+
project_id = data.vercel_project.example.id
25+
name = "example-custom-env"
26+
}
27+
```
28+
29+
<!-- schema generated by tfplugindocs -->
30+
## Schema
31+
32+
### Required
33+
34+
- `name` (String) The name of the environment.
35+
- `project_id` (String) The ID of the existing Vercel Project.
36+
37+
### Optional
38+
39+
- `team_id` (String) The team ID to add the project to. Required when configuring a team resource if a default team has not been set in the provider.
40+
41+
### Read-Only
42+
43+
- `branch_tracking` (Attributes) The branch tracking configuration for the environment. When enabled, each qualifying merge will generate a deployment. (see [below for nested schema](#nestedatt--branch_tracking))
44+
- `description` (String) A description of what the environment is.
45+
- `id` (String) The ID of the environment.
46+
47+
<a id="nestedatt--branch_tracking"></a>
48+
### Nested Schema for `branch_tracking`
49+
50+
Read-Only:
51+
52+
- `pattern` (String) The pattern of the branch name to track.
53+
- `type` (String) How a branch name should be matched against the pattern. Must be one of 'startsWith', 'endsWith' or 'equals'.

docs/data-sources/project.md

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ data "vercel_project" "example" {
7575
Read-Only:
7676

7777
- `comment` (String) A comment explaining what the environment variable is for.
78+
- `custom_environment_ids` (Set of String) The IDs of Custom Environments that the Environment Variable should be present on.
7879
- `git_branch` (String) The git branch of the environment variable.
7980
- `id` (String) The ID of the environment variable
8081
- `key` (String) The name of the environment variable.

0 commit comments

Comments
 (0)