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

Log out environment variable conflicts #232

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 1 commit into from
Oct 31, 2024
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
76 changes: 75 additions & 1 deletion client/environment_variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,71 @@ func (c *Client) CreateEnvironmentVariable(ctx context.Context, request CreateEn
url: url,
body: payload,
}, &e)
if conflictingEnv, isConflicting, err2 := conflictingEnvVar(err); isConflicting {
if err2 != nil {
return e, err2
}
id, err3 := c.findConflictingEnvID(ctx, request.TeamID, request.ProjectID, conflictingEnv)
if err3 != nil {
return e, fmt.Errorf("%w %s", err, err3)
}
return e, fmt.Errorf("%w the conflicting environment variable ID is %s", err, id)
}
if err != nil {
return e, err
}
// 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
}

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)
if c.teamID(teamID) != "" {
url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(teamID))
}

response := struct {
Envs []EnvironmentVariable `json:"envs"`
}{}
err = c.doRequest(clientRequest{
ctx: ctx,
method: "GET",
url: url,
}, &response)
return response.Envs, err
}

func overlaps(s []string, e []string) bool {
for _, a := range s {
for _, b := range e {
if a == b {
return true
}
}
}
return false
}

func (c *Client) findConflictingEnvID(ctx context.Context, teamID, projectID string, envConflict EnvConflictError) (string, error) {
envs, err := c.ListEnvironmentVariables(ctx, teamID, projectID)
if err != nil {
return "", fmt.Errorf("unable to list environment variables to detect conflict: %w", err)
}

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, nil
}
}
return "", fmt.Errorf("conflicting environment variable not found")
}

type CreateEnvironmentVariablesRequest struct {
EnvironmentVariables []EnvironmentVariableRequest
ProjectID string
Expand All @@ -64,12 +123,27 @@ func (c *Client) CreateEnvironmentVariables(ctx context.Context, request CreateE
"url": url,
"payload": payload,
})
return c.doRequest(clientRequest{
err := c.doRequest(clientRequest{
ctx: ctx,
method: "POST",
url: url,
body: payload,
}, nil)
if conflictingEnv, isConflicting, err2 := conflictingEnvVar(err); isConflicting {
if err2 != nil {
return err2
}
id, err3 := c.findConflictingEnvID(ctx, request.TeamID, request.ProjectID, conflictingEnv)
if err3 != nil {
return fmt.Errorf("%w %s", err, err3)
}
return fmt.Errorf("%w the conflicting environment variable ID is %s", err, id)
}
if err != nil {
return err
}

return err
}

// UpdateEnvironmentVariableRequest defines the information that needs to be passed to Vercel in order to
Expand Down
32 changes: 31 additions & 1 deletion client/error.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package client

import "errors"
import (
"encoding/json"
"errors"
)

// NotFound detects if an error returned by the Vercel API was the result of an entity not existing.
func NotFound(err error) bool {
Expand All @@ -12,3 +15,30 @@ func noContent(err error) bool {
var apiErr APIError
return err != nil && errors.As(err, &apiErr) && apiErr.StatusCode == 204
}

func conflictingSharedEnv(err error) bool {
var apiErr APIError
return err != nil && errors.As(err, &apiErr) && apiErr.StatusCode == 409 && apiErr.Code == "existing_key_and_target"
}

type EnvConflictError struct {
Code string `json:"code"`
Message string `json:"message"`
Key string `json:"key"`
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"
if !conflict {
return envConflictError, false, err
}

var conflictErr struct {
Error EnvConflictError `json:"error"`
}
_ = json.Unmarshal(apiErr.RawMessage, &conflictErr)
return conflictErr.Error, true, err
}
29 changes: 29 additions & 0 deletions client/shared_environment_variable.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,28 @@ type CreateSharedEnvironmentVariableRequest struct {
TeamID string
}

func (c *Client) findConflictingSharedEnvID(ctx context.Context, request CreateSharedEnvironmentVariableRequest) (string, error) {
envs, err := c.ListSharedEnvironmentVariables(ctx, request.TeamID)
if err != nil {
return "", fmt.Errorf("unable to list shared environment variables to detect conflict: %w", err)
}
if len(request.EnvironmentVariable.EnvironmentVariables) != 1 {
return "", fmt.Errorf("cannot detect conflict for multiple shared environment variables")
}
requestedEnv := request.EnvironmentVariable.EnvironmentVariables[0]

for _, env := range envs {
if env.Key == requestedEnv.Key && overlaps(env.Target, request.EnvironmentVariable.Target) {
id := env.ID
if request.TeamID != "" {
id = fmt.Sprintf("%s/%s", request.TeamID, id)
}
return id, nil
}
}
return "", fmt.Errorf("conflicting shared environment variable not found")
}

// CreateSharedEnvironmentVariable will create a brand new shared environment variable if one does not exist.
func (c *Client) CreateSharedEnvironmentVariable(ctx context.Context, request CreateSharedEnvironmentVariableRequest) (e SharedEnvironmentVariableResponse, err error) {
url := fmt.Sprintf("%s/v1/env", c.baseURL)
Expand All @@ -56,6 +78,13 @@ func (c *Client) CreateSharedEnvironmentVariable(ctx context.Context, request Cr
url: url,
body: payload,
}, &response)
if conflictingSharedEnv(err) {
id, err2 := c.findConflictingSharedEnvID(ctx, request)
if err2 != nil {
return e, fmt.Errorf("%w %s", err, err2)
}
return e, fmt.Errorf("%w the conflicting shared environment variable ID is %s", err, id)
}
if err != nil {
return e, err
}
Expand Down