diff --git a/client/environment_variable.go b/client/environment_variable.go index e97fba04..562c3ce8 100644 --- a/client/environment_variable.go +++ b/client/environment_variable.go @@ -12,7 +12,7 @@ import ( type EnvironmentVariableRequest struct { Key string `json:"key"` Value string `json:"value"` - Target []string `json:"target,omitempty"` + Target []string `json:"target"` CustomEnvironmentIDs []string `json:"customEnvironmentIds,omitempty"` GitBranch *string `json:"gitBranch,omitempty"` Type string `json:"type"` @@ -27,7 +27,7 @@ type CreateEnvironmentVariableRequest struct { // CreateEnvironmentVariable will create a brand new environment variable if one does not exist. func (c *Client) CreateEnvironmentVariable(ctx context.Context, request CreateEnvironmentVariableRequest) (e EnvironmentVariable, err error) { - url := fmt.Sprintf("%s/v9/projects/%s/env", c.baseURL, request.ProjectID) + url := fmt.Sprintf("%s/v10/projects/%s/env", c.baseURL, request.ProjectID) if c.teamID(request.TeamID) != "" { url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID)) } @@ -37,38 +37,38 @@ func (c *Client) CreateEnvironmentVariable(ctx context.Context, request CreateEn "url": url, "payload": payload, }) + var response CreateEnvironmentVariableResponse err = c.doRequest(clientRequest{ ctx: ctx, method: "POST", url: url, body: payload, - }, &e) + }, &response) + if conflictingEnv, isConflicting, err2 := conflictingEnvVar(err); isConflicting { if err2 != nil { return e, err2 } - envs, err3 := c.ListEnvironmentVariables(ctx, request.TeamID, request.ProjectID) - if err != nil { + if err3 != nil { return e, fmt.Errorf("%s: unable to list environment variables to detect conflict: %s", err, err3) } - id, found := findConflictingEnvID(request.TeamID, request.ProjectID, conflictingEnv, envs) if found { return e, fmt.Errorf("%w the conflicting environment variable ID is %s", err, id) } } + if err != nil { return e, fmt.Errorf("%w - %s", err, payload) } - // The API response returns an encrypted environment variable, but we want to return the decrypted version. - e.Value = request.EnvironmentVariable.Value - e.TeamID = c.teamID(request.TeamID) - return e, err + response.Created.Value = request.EnvironmentVariable.Value + response.Created.TeamID = c.teamID(request.TeamID) + return response.Created, err } func (c *Client) ListEnvironmentVariables(ctx context.Context, teamID, projectID string) (envs []EnvironmentVariable, err error) { - url := fmt.Sprintf("%s/v9/projects/%s/env", c.baseURL, projectID) + url := fmt.Sprintf("%s/v10/projects/%s/env", c.baseURL, projectID) if c.teamID(teamID) != "" { url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(teamID)) } @@ -85,26 +85,39 @@ func (c *Client) ListEnvironmentVariables(ctx context.Context, teamID, projectID } func overlaps(s []string, e []string) bool { + set := make(map[string]struct{}, len(s)) for _, a := range s { - for _, b := range e { - if a == b { - return true - } + set[a] = struct{}{} + } + + for _, b := range e { + if _, exists := set[b]; exists { + return true } } + return false } func findConflictingEnvID(teamID, projectID string, envConflict EnvConflictError, envs []EnvironmentVariable) (string, bool) { + checkTargetOverlap := len(envConflict.Target) != 0 + for _, env := range envs { - if env.Key == envConflict.Key && overlaps(env.Target, envConflict.Target) && env.GitBranch == envConflict.GitBranch { - id := fmt.Sprintf("%s/%s", projectID, env.ID) - if teamID != "" { - id = fmt.Sprintf("%s/%s", teamID, id) - } - return id, true + if env.Key != envConflict.EnvVarKey || env.GitBranch != envConflict.GitBranch { + continue + } + + if checkTargetOverlap && !overlaps(env.Target, envConflict.Target) { + continue + } + + id := fmt.Sprintf("%s/%s", projectID, env.ID) + if teamID != "" { + id = fmt.Sprintf("%s/%s", teamID, id) } + return id, true } + return "", false } @@ -116,15 +129,28 @@ type CreateEnvironmentVariablesRequest struct { type CreateEnvironmentVariablesResponse struct { Created []EnvironmentVariable `json:"created"` - Failed []struct { - Error struct { - Code string `json:"code"` - Message string `json:"message"` - Key string `json:"envVarKey"` - GitBranch *string `json:"gitBranch"` - Target []string `json:"target"` - } `json:"error"` - } `json:"failed"` + Failed []FailedItem `json:"failed"` +} + +type FailedItem struct { + Error struct { + Action *string `json:"action,omitempty"` + Code string `json:"code"` + EnvVarID *string `json:"envVarId,omitempty"` + EnvVarKey *string `json:"envVarKey,omitempty"` + GitBranch *string `json:"gitBranch,omitempty"` + Key *string `json:"key,omitempty"` + Link *string `json:"link,omitempty"` + Message string `json:"message"` + Project *string `json:"project,omitempty"` + Target []string `json:"target,omitempty"` + Value *string `json:"value,omitempty"` + } `json:"error"` +} + +type CreateEnvironmentVariableResponse struct { + Created EnvironmentVariable `json:"created"` + Failed []FailedItem `json:"failed"` } func (c *Client) CreateEnvironmentVariables(ctx context.Context, request CreateEnvironmentVariablesRequest) ([]EnvironmentVariable, error) { @@ -141,10 +167,6 @@ func (c *Client) CreateEnvironmentVariables(ctx context.Context, request CreateE url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID)) } payload := string(mustMarshal(request.EnvironmentVariables)) - tflog.Info(ctx, "creating environment variables", map[string]interface{}{ - "url": url, - "payload": payload, - }) var response CreateEnvironmentVariablesResponse err := c.doRequest(clientRequest{ @@ -165,7 +187,7 @@ func (c *Client) CreateEnvironmentVariables(ctx context.Context, request CreateE for _, failed := range response.Failed { if failed.Error.Code == "ENV_CONFLICT" { id, found := findConflictingEnvID(request.TeamID, request.ProjectID, EnvConflictError{ - Key: failed.Error.Key, + Key: *failed.Error.EnvVarKey, Target: failed.Error.Target, GitBranch: failed.Error.GitBranch, }, envs) @@ -198,7 +220,7 @@ type UpdateEnvironmentVariableRequest struct { // UpdateEnvironmentVariable will update an existing environment variable to the latest information. func (c *Client) UpdateEnvironmentVariable(ctx context.Context, request UpdateEnvironmentVariableRequest) (e EnvironmentVariable, err error) { - url := fmt.Sprintf("%s/v9/projects/%s/env/%s", c.baseURL, request.ProjectID, request.EnvID) + url := fmt.Sprintf("%s/v10/projects/%s/env/%s", c.baseURL, request.ProjectID, request.EnvID) if c.teamID(request.TeamID) != "" { url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID)) } @@ -262,7 +284,7 @@ func (c *Client) GetEnvironmentVariables(ctx context.Context, projectID, teamID // GetEnvironmentVariable gets a singluar environment variable from Vercel based on its ID. func (c *Client) GetEnvironmentVariable(ctx context.Context, projectID, teamID, envID string) (e EnvironmentVariable, err error) { - url := fmt.Sprintf("%s/v1/projects/%s/env/%s", c.baseURL, projectID, envID) + url := fmt.Sprintf("%s/v10/projects/%s/env/%s", c.baseURL, projectID, envID) if c.teamID(teamID) != "" { url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(teamID)) } diff --git a/client/error.go b/client/error.go index 1e051171..881c87a3 100644 --- a/client/error.go +++ b/client/error.go @@ -25,13 +25,14 @@ type EnvConflictError struct { Code string `json:"code"` Message string `json:"message"` Key string `json:"key"` + EnvVarKey string `json:"envVarKey"` Target []string `json:"target"` GitBranch *string `json:"gitBranch"` } func conflictingEnvVar(e error) (envConflictError EnvConflictError, ok bool, err error) { var apiErr APIError - conflict := e != nil && errors.As(e, &apiErr) && apiErr.StatusCode == 403 && apiErr.Code == "ENV_ALREADY_EXISTS" + conflict := e != nil && errors.As(e, &apiErr) && apiErr.StatusCode == 400 && apiErr.Code == "ENV_CONFLICT" if !conflict { return envConflictError, false, err } diff --git a/vercel/resource_project_environment_variable.go b/vercel/resource_project_environment_variable.go index 7555b1ae..33b65254 100644 --- a/vercel/resource_project_environment_variable.go +++ b/vercel/resource_project_environment_variable.go @@ -82,7 +82,6 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ ElementType: types.StringType, Validators: []validator.Set{ setvalidator.ValueStringsAre(stringvalidator.OneOf("production", "preview", "development")), - setvalidator.SizeAtLeast(1), setvalidator.AtLeastOneOf( path.MatchRoot("custom_environment_ids"), path.MatchRoot("target"),