Skip to content

Commit bb4cd02

Browse files
dglsparsonsqlimenoqueartemlive
authored
Upgrade environment variables API to v10 (#260)
* fix: api version to v10 * refactor: duplicated variable id finding logic * fix: use plain format in state for sensitive env variables * Fix omission of target field in API request --------- Co-authored-by: qlimenoque <qlimenoque@macpaw.com> Co-authored-by: Artem Miroshnychenko <itsysadmin9@gmail.com> Co-authored-by: qlimenoque <49155800+qlimenoque@users.noreply.github.com>
1 parent 8594dc6 commit bb4cd02

File tree

3 files changed

+65
-39
lines changed

3 files changed

+65
-39
lines changed

client/environment_variable.go

Lines changed: 63 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type CreateEnvironmentVariableRequest struct {
2727

2828
// CreateEnvironmentVariable will create a brand new environment variable if one does not exist.
2929
func (c *Client) CreateEnvironmentVariable(ctx context.Context, request CreateEnvironmentVariableRequest) (e EnvironmentVariable, err error) {
30-
url := fmt.Sprintf("%s/v9/projects/%s/env", c.baseURL, request.ProjectID)
30+
url := fmt.Sprintf("%s/v10/projects/%s/env", c.baseURL, request.ProjectID)
3131
if c.teamID(request.TeamID) != "" {
3232
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID))
3333
}
@@ -37,38 +37,38 @@ func (c *Client) CreateEnvironmentVariable(ctx context.Context, request CreateEn
3737
"url": url,
3838
"payload": payload,
3939
})
40+
var response CreateEnvironmentVariableResponse
4041
err = c.doRequest(clientRequest{
4142
ctx: ctx,
4243
method: "POST",
4344
url: url,
4445
body: payload,
45-
}, &e)
46+
}, &response)
47+
4648
if conflictingEnv, isConflicting, err2 := conflictingEnvVar(err); isConflicting {
4749
if err2 != nil {
4850
return e, err2
4951
}
50-
5152
envs, err3 := c.ListEnvironmentVariables(ctx, request.TeamID, request.ProjectID)
52-
if err != nil {
53+
if err3 != nil {
5354
return e, fmt.Errorf("%s: unable to list environment variables to detect conflict: %s", err, err3)
5455
}
55-
5656
id, found := findConflictingEnvID(request.TeamID, request.ProjectID, conflictingEnv, envs)
5757
if found {
5858
return e, fmt.Errorf("%w the conflicting environment variable ID is %s", err, id)
5959
}
6060
}
61+
6162
if err != nil {
6263
return e, fmt.Errorf("%w - %s", err, payload)
6364
}
64-
// The API response returns an encrypted environment variable, but we want to return the decrypted version.
65-
e.Value = request.EnvironmentVariable.Value
66-
e.TeamID = c.teamID(request.TeamID)
67-
return e, err
65+
response.Created.Value = request.EnvironmentVariable.Value
66+
response.Created.TeamID = c.teamID(request.TeamID)
67+
return response.Created, err
6868
}
6969

7070
func (c *Client) ListEnvironmentVariables(ctx context.Context, teamID, projectID string) (envs []EnvironmentVariable, err error) {
71-
url := fmt.Sprintf("%s/v9/projects/%s/env", c.baseURL, projectID)
71+
url := fmt.Sprintf("%s/v10/projects/%s/env", c.baseURL, projectID)
7272
if c.teamID(teamID) != "" {
7373
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(teamID))
7474
}
@@ -85,26 +85,39 @@ func (c *Client) ListEnvironmentVariables(ctx context.Context, teamID, projectID
8585
}
8686

8787
func overlaps(s []string, e []string) bool {
88+
set := make(map[string]struct{}, len(s))
8889
for _, a := range s {
89-
for _, b := range e {
90-
if a == b {
91-
return true
92-
}
90+
set[a] = struct{}{}
91+
}
92+
93+
for _, b := range e {
94+
if _, exists := set[b]; exists {
95+
return true
9396
}
9497
}
98+
9599
return false
96100
}
97101

98102
func findConflictingEnvID(teamID, projectID string, envConflict EnvConflictError, envs []EnvironmentVariable) (string, bool) {
103+
checkTargetOverlap := len(envConflict.Target) != 0
104+
99105
for _, env := range envs {
100-
if env.Key == envConflict.Key && overlaps(env.Target, envConflict.Target) && env.GitBranch == envConflict.GitBranch {
101-
id := fmt.Sprintf("%s/%s", projectID, env.ID)
102-
if teamID != "" {
103-
id = fmt.Sprintf("%s/%s", teamID, id)
104-
}
105-
return id, true
106+
if env.Key != envConflict.EnvVarKey || env.GitBranch != envConflict.GitBranch {
107+
continue
108+
}
109+
110+
if checkTargetOverlap && !overlaps(env.Target, envConflict.Target) {
111+
continue
106112
}
113+
114+
id := fmt.Sprintf("%s/%s", projectID, env.ID)
115+
if teamID != "" {
116+
id = fmt.Sprintf("%s/%s", teamID, id)
117+
}
118+
return id, true
107119
}
120+
108121
return "", false
109122
}
110123

@@ -116,15 +129,28 @@ type CreateEnvironmentVariablesRequest struct {
116129

117130
type CreateEnvironmentVariablesResponse struct {
118131
Created []EnvironmentVariable `json:"created"`
119-
Failed []struct {
120-
Error struct {
121-
Code string `json:"code"`
122-
Message string `json:"message"`
123-
Key string `json:"envVarKey"`
124-
GitBranch *string `json:"gitBranch"`
125-
Target []string `json:"target"`
126-
} `json:"error"`
127-
} `json:"failed"`
132+
Failed []FailedItem `json:"failed"`
133+
}
134+
135+
type FailedItem struct {
136+
Error struct {
137+
Action *string `json:"action,omitempty"`
138+
Code string `json:"code"`
139+
EnvVarID *string `json:"envVarId,omitempty"`
140+
EnvVarKey *string `json:"envVarKey,omitempty"`
141+
GitBranch *string `json:"gitBranch,omitempty"`
142+
Key *string `json:"key,omitempty"`
143+
Link *string `json:"link,omitempty"`
144+
Message string `json:"message"`
145+
Project *string `json:"project,omitempty"`
146+
Target []string `json:"target,omitempty"`
147+
Value *string `json:"value,omitempty"`
148+
} `json:"error"`
149+
}
150+
151+
type CreateEnvironmentVariableResponse struct {
152+
Created EnvironmentVariable `json:"created"`
153+
Failed []FailedItem `json:"failed"`
128154
}
129155

130156
func (c *Client) CreateEnvironmentVariables(ctx context.Context, request CreateEnvironmentVariablesRequest) ([]EnvironmentVariable, error) {
@@ -141,10 +167,6 @@ func (c *Client) CreateEnvironmentVariables(ctx context.Context, request CreateE
141167
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID))
142168
}
143169
payload := string(mustMarshal(request.EnvironmentVariables))
144-
tflog.Info(ctx, "creating environment variables", map[string]interface{}{
145-
"url": url,
146-
"payload": payload,
147-
})
148170

149171
var response CreateEnvironmentVariablesResponse
150172
err := c.doRequest(clientRequest{
@@ -165,15 +187,19 @@ func (c *Client) CreateEnvironmentVariables(ctx context.Context, request CreateE
165187
for _, failed := range response.Failed {
166188
if failed.Error.Code == "ENV_CONFLICT" {
167189
id, found := findConflictingEnvID(request.TeamID, request.ProjectID, EnvConflictError{
168-
Key: failed.Error.Key,
190+
Key: *failed.Error.EnvVarKey,
169191
Target: failed.Error.Target,
170192
GitBranch: failed.Error.GitBranch,
171193
}, envs)
172194
if found {
173195
err = fmt.Errorf("%w, conflicting environment variable ID is %s", err, id)
174196
}
175197
} else {
176-
err = fmt.Errorf("failed to create environment variables, %s %s %s", failed.Error.Message, failed.Error.Key, failed.Error.Target)
198+
key := ""
199+
if failed.Error.Key != nil {
200+
key = *failed.Error.Key
201+
}
202+
err = fmt.Errorf("failed to create environment variables, %s %s %s", failed.Error.Message, key, failed.Error.Target)
177203
}
178204
}
179205
return response.Created, err
@@ -198,7 +224,7 @@ type UpdateEnvironmentVariableRequest struct {
198224

199225
// UpdateEnvironmentVariable will update an existing environment variable to the latest information.
200226
func (c *Client) UpdateEnvironmentVariable(ctx context.Context, request UpdateEnvironmentVariableRequest) (e EnvironmentVariable, err error) {
201-
url := fmt.Sprintf("%s/v9/projects/%s/env/%s", c.baseURL, request.ProjectID, request.EnvID)
227+
url := fmt.Sprintf("%s/v10/projects/%s/env/%s", c.baseURL, request.ProjectID, request.EnvID)
202228
if c.teamID(request.TeamID) != "" {
203229
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID))
204230
}
@@ -262,7 +288,7 @@ func (c *Client) GetEnvironmentVariables(ctx context.Context, projectID, teamID
262288

263289
// GetEnvironmentVariable gets a singluar environment variable from Vercel based on its ID.
264290
func (c *Client) GetEnvironmentVariable(ctx context.Context, projectID, teamID, envID string) (e EnvironmentVariable, err error) {
265-
url := fmt.Sprintf("%s/v1/projects/%s/env/%s", c.baseURL, projectID, envID)
291+
url := fmt.Sprintf("%s/v10/projects/%s/env/%s", c.baseURL, projectID, envID)
266292
if c.teamID(teamID) != "" {
267293
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(teamID))
268294
}

client/error.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ type EnvConflictError struct {
2525
Code string `json:"code"`
2626
Message string `json:"message"`
2727
Key string `json:"key"`
28+
EnvVarKey string `json:"envVarKey"`
2829
Target []string `json:"target"`
2930
GitBranch *string `json:"gitBranch"`
3031
}
3132

3233
func conflictingEnvVar(e error) (envConflictError EnvConflictError, ok bool, err error) {
3334
var apiErr APIError
34-
conflict := e != nil && errors.As(e, &apiErr) && apiErr.StatusCode == 403 && apiErr.Code == "ENV_ALREADY_EXISTS"
35+
conflict := e != nil && errors.As(e, &apiErr) && apiErr.StatusCode == 400 && apiErr.Code == "ENV_CONFLICT"
3536
if !conflict {
3637
return envConflictError, false, err
3738
}

vercel/resource_project_environment_variable.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ
8282
ElementType: types.StringType,
8383
Validators: []validator.Set{
8484
setvalidator.ValueStringsAre(stringvalidator.OneOf("production", "preview", "development")),
85-
setvalidator.SizeAtLeast(1),
8685
setvalidator.AtLeastOneOf(
8786
path.MatchRoot("custom_environment_ids"),
8887
path.MatchRoot("target"),

0 commit comments

Comments
 (0)