From 1fd43f46513e8c827c42d96f0f87e333a65708c2 Mon Sep 17 00:00:00 2001 From: Ana Jovanova Date: Sun, 28 Jan 2024 22:28:57 +0100 Subject: [PATCH 1/7] Add support for sensitive environment variable --- docs/resources/project.md | 1 + .../resources/project_environment_variable.md | 11 +++++++ docs/resources/shared_environment_variable.md | 1 + .../resource.tf | 10 +++++++ vercel/resource_project.go | 6 ++++ .../resource_project_environment_variable.go | 6 ++++ ...urce_project_environment_variable_model.go | 22 ++++++++++++-- ...ource_project_environment_variable_test.go | 30 +++++++++++++++++++ vercel/resource_project_model.go | 22 ++++++++++++-- .../resource_shared_environment_variable.go | 6 ++++ ...ource_shared_environment_variable_model.go | 21 +++++++++++-- ...source_shared_environment_variable_test.go | 16 ++++++++++ 12 files changed, 146 insertions(+), 6 deletions(-) diff --git a/docs/resources/project.md b/docs/resources/project.md index dabfb28e..a9989382 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -89,6 +89,7 @@ Required: Optional: - `git_branch` (String) The git branch of the Environment Variable. +- `sensitive` (Boolean) Whether the Environment Variable is sensitive or not. Read-Only: diff --git a/docs/resources/project_environment_variable.md b/docs/resources/project_environment_variable.md index 417cd874..a83f0340 100644 --- a/docs/resources/project_environment_variable.md +++ b/docs/resources/project_environment_variable.md @@ -51,6 +51,16 @@ resource "vercel_project_environment_variable" "example_git_branch" { target = ["preview"] git_branch = "staging" } + +# A sensitive environment variable that will be created +# for this project for the "production" environment. +resource "vercel_project_environment_variable" "example_sensitive" { + project_id = vercel_project.example.id + key = "foo" + value = "bar-production" + target = ["production"] + sensitive = true +} ``` @@ -66,6 +76,7 @@ resource "vercel_project_environment_variable" "example_git_branch" { ### Optional - `git_branch` (String) The git branch of the Environment Variable. +- `sensitive` (Boolean) Whether the Environment Variable is sensitive or not. - `team_id` (String) The ID of the Vercel team.Required when configuring a team resource if a default team has not been set in the provider. ### Read-Only diff --git a/docs/resources/shared_environment_variable.md b/docs/resources/shared_environment_variable.md index 1d3e5537..aa0d49ad 100644 --- a/docs/resources/shared_environment_variable.md +++ b/docs/resources/shared_environment_variable.md @@ -52,6 +52,7 @@ resource "vercel_shared_environment_variable" "example" { ### Optional +- `sensitive` (Boolean) Whether the Environment Variable is sensitive or not. - `team_id` (String) The ID of the Vercel team. Shared environment variables require a team. ### Read-Only diff --git a/examples/resources/vercel_project_environment_variable/resource.tf b/examples/resources/vercel_project_environment_variable/resource.tf index bb85fa1d..a6e6a1c9 100644 --- a/examples/resources/vercel_project_environment_variable/resource.tf +++ b/examples/resources/vercel_project_environment_variable/resource.tf @@ -25,3 +25,13 @@ resource "vercel_project_environment_variable" "example_git_branch" { target = ["preview"] git_branch = "staging" } + +# A sensitive environment variable that will be created +# for this project for the "production" environment. +resource "vercel_project_environment_variable" "example_sensitive" { + project_id = vercel_project.example.id + key = "foo" + value = "bar-production" + target = ["production"] + sensitive = true +} \ No newline at end of file diff --git a/vercel/resource_project.go b/vercel/resource_project.go index bb1d79f1..e095e3fc 100644 --- a/vercel/resource_project.go +++ b/vercel/resource_project.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -139,6 +140,11 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ Description: "The ID of the Environment Variable.", Computed: true, }, + "sensitive": schema.BoolAttribute{ + Description: "Whether the Environment Variable is sensitive or not.", + Optional: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.RequiresReplace()}, + }, }, }, }, diff --git a/vercel/resource_project_environment_variable.go b/vercel/resource_project_environment_variable.go index 604c07c1..8cbc371b 100644 --- a/vercel/resource_project_environment_variable.go +++ b/vercel/resource_project_environment_variable.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -103,6 +104,11 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace(), stringplanmodifier.UseStateForUnknown()}, Computed: true, }, + "sensitive": schema.BoolAttribute{ + Description: "Whether the Environment Variable is sensitive or not.", + Optional: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.RequiresReplace()}, + }, }, } } diff --git a/vercel/resource_project_environment_variable_model.go b/vercel/resource_project_environment_variable_model.go index a7bd6588..37966a55 100644 --- a/vercel/resource_project_environment_variable_model.go +++ b/vercel/resource_project_environment_variable_model.go @@ -14,6 +14,7 @@ type ProjectEnvironmentVariable struct { TeamID types.String `tfsdk:"team_id"` ProjectID types.String `tfsdk:"project_id"` ID types.String `tfsdk:"id"` + Sensitive types.Bool `tfsdk:"sensitive"` } func (e *ProjectEnvironmentVariable) toCreateEnvironmentVariableRequest() client.CreateEnvironmentVariableRequest { @@ -21,13 +22,21 @@ func (e *ProjectEnvironmentVariable) toCreateEnvironmentVariableRequest() client for _, t := range e.Target { target = append(target, t.ValueString()) } + var envVariableType string + + if e.Sensitive.ValueBool() { + envVariableType = "sensitive" + } else { + envVariableType = "encrypted" + } + return client.CreateEnvironmentVariableRequest{ EnvironmentVariable: client.EnvironmentVariableRequest{ Key: e.Key.ValueString(), Value: e.Value.ValueString(), Target: target, GitBranch: toStrPointer(e.GitBranch), - Type: "encrypted", + Type: envVariableType, }, ProjectID: e.ProjectID.ValueString(), TeamID: e.TeamID.ValueString(), @@ -39,12 +48,21 @@ func (e *ProjectEnvironmentVariable) toUpdateEnvironmentVariableRequest() client for _, t := range e.Target { target = append(target, t.ValueString()) } + + var envVariableType string + + if e.Sensitive.ValueBool() { + envVariableType = "sensitive" + } else { + envVariableType = "encrypted" + } + return client.UpdateEnvironmentVariableRequest{ Key: e.Key.ValueString(), Value: e.Value.ValueString(), Target: target, GitBranch: toStrPointer(e.GitBranch), - Type: "encrypted", + Type: envVariableType, ProjectID: e.ProjectID.ValueString(), TeamID: e.TeamID.ValueString(), EnvID: e.ID.ValueString(), diff --git a/vercel/resource_project_environment_variable_test.go b/vercel/resource_project_environment_variable_test.go index 915e3515..07731094 100644 --- a/vercel/resource_project_environment_variable_test.go +++ b/vercel/resource_project_environment_variable_test.go @@ -72,6 +72,12 @@ func TestAcc_ProjectEnvironmentVariables(t *testing.T) { resource.TestCheckResourceAttr("vercel_project_environment_variable.example_git_branch", "value", "bar-staging"), resource.TestCheckTypeSetElemAttr("vercel_project_environment_variable.example_git_branch", "target.*", "preview"), resource.TestCheckResourceAttr("vercel_project_environment_variable.example_git_branch", "git_branch", "production"), + + testAccProjectEnvironmentVariableExists("vercel_project_environment_variable.example_sensitive", testTeam()), + resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "key", "foo"), + resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "value", "bar-production"), + resource.TestCheckTypeSetElemAttr("vercel_project_environment_variable.example_sensitive", "target.*", "production"), + resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "sensitive", "true"), ), }, { @@ -102,6 +108,12 @@ func TestAcc_ProjectEnvironmentVariables(t *testing.T) { ImportStateVerify: true, ImportStateIdFunc: getProjectEnvironmentVariableImportID("vercel_project_environment_variable.example_git_branch"), }, + { + ResourceName: "vercel_project_environment_variable.example_sensitive", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: getProjectEnvironmentVariableImportID("vercel_project_environment_variable.example_sensitive"), + }, { Config: testAccProjectEnvironmentVariablesConfigDeleted(nameSuffix), Check: resource.ComposeAggregateTestCheckFunc( @@ -158,6 +170,15 @@ resource "vercel_project_environment_variable" "example_git_branch" { target = ["preview"] git_branch = "production" } + +resource "vercel_project_environment_variable" "example_sensitive" { + project_id = vercel_project.example.id + %[3]s + key = "foo" + value = "bar-production" + target = ["production"] + sensitive = true +} `, projectName, testGithubRepo(), teamIDConfig()) } @@ -189,6 +210,15 @@ resource "vercel_project_environment_variable" "example_git_branch" { target = ["preview"] git_branch = "test" } + +resource "vercel_project_environment_variable" "example_sensitive" { + project_id = vercel_project.example.id + %[3]s + key = "foo-2" + value = "bar-production-2" + target = ["production"] + sensitive = true +} `, projectName, testGithubRepo(), teamIDConfig()) } diff --git a/vercel/resource_project_model.go b/vercel/resource_project_model.go index ae4d8669..a776a0d4 100644 --- a/vercel/resource_project_model.go +++ b/vercel/resource_project_model.go @@ -63,12 +63,20 @@ func parseEnvironment(vars []EnvironmentItem) []client.EnvironmentVariable { target = append(target, t.ValueString()) } + var envVariableType string + + if e.Sensitive.ValueBool() { + envVariableType = "sensitive" + } else { + envVariableType = "encrypted" + } + out = append(out, client.EnvironmentVariable{ Key: e.Key.ValueString(), Value: e.Value.ValueString(), Target: target, GitBranch: toStrPointer(e.GitBranch), - Type: "encrypted", + Type: envVariableType, ID: e.ID.ValueString(), }) } @@ -122,6 +130,7 @@ type EnvironmentItem struct { Key types.String `tfsdk:"key"` Value types.String `tfsdk:"value"` ID types.String `tfsdk:"id"` + Sensitive types.Bool `tfsdk:"sensitive"` } func (e *EnvironmentItem) toEnvironmentVariableRequest() client.EnvironmentVariableRequest { @@ -129,12 +138,21 @@ func (e *EnvironmentItem) toEnvironmentVariableRequest() client.EnvironmentVaria for _, t := range e.Target { target = append(target, t.ValueString()) } + + var envVariableType string + + if e.Sensitive.ValueBool() { + envVariableType = "sensitive" + } else { + envVariableType = "encrypted" + } + return client.EnvironmentVariableRequest{ Key: e.Key.ValueString(), Value: e.Value.ValueString(), Target: target, GitBranch: toStrPointer(e.GitBranch), - Type: "encrypted", + Type: envVariableType, } } diff --git a/vercel/resource_shared_environment_variable.go b/vercel/resource_shared_environment_variable.go index 5ea50417..8b8f61df 100644 --- a/vercel/resource_shared_environment_variable.go +++ b/vercel/resource_shared_environment_variable.go @@ -7,6 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" @@ -96,6 +97,11 @@ For more detailed information, please see the [Vercel documentation](https://ver PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace(), stringplanmodifier.UseStateForUnknown()}, Computed: true, }, + "sensitive": schema.BoolAttribute{ + Description: "Whether the Environment Variable is sensitive or not.", + Optional: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.RequiresReplace()}, + }, }, } } diff --git a/vercel/resource_shared_environment_variable_model.go b/vercel/resource_shared_environment_variable_model.go index 8462e768..4705a6a3 100644 --- a/vercel/resource_shared_environment_variable_model.go +++ b/vercel/resource_shared_environment_variable_model.go @@ -13,6 +13,7 @@ type SharedEnvironmentVariable struct { TeamID types.String `tfsdk:"team_id"` ProjectIDs []types.String `tfsdk:"project_ids"` ID types.String `tfsdk:"id"` + Sensitive types.Bool `tfsdk:"sensitive"` } func (e *SharedEnvironmentVariable) toCreateSharedEnvironmentVariableRequest() client.CreateSharedEnvironmentVariableRequest { @@ -24,10 +25,19 @@ func (e *SharedEnvironmentVariable) toCreateSharedEnvironmentVariableRequest() c for _, t := range e.ProjectIDs { projectIDs = append(projectIDs, t.ValueString()) } + + var envVariableType string + + if e.Sensitive.ValueBool() { + envVariableType = "sensitive" + } else { + envVariableType = "encrypted" + } + return client.CreateSharedEnvironmentVariableRequest{ EnvironmentVariable: client.SharedEnvironmentVariableRequest{ Target: target, - Type: "encrypted", + Type: envVariableType, ProjectIDs: projectIDs, EnvironmentVariables: []client.SharedEnvVarRequest{ { @@ -49,11 +59,18 @@ func (e *SharedEnvironmentVariable) toUpdateSharedEnvironmentVariableRequest() c for _, t := range e.ProjectIDs { projectIDs = append(projectIDs, t.ValueString()) } + var envVariableType string + + if e.Sensitive.ValueBool() { + envVariableType = "sensitive" + } else { + envVariableType = "encrypted" + } return client.UpdateSharedEnvironmentVariableRequest{ Key: e.Key.ValueString(), Value: e.Value.ValueString(), Target: target, - Type: "encrypted", + Type: envVariableType, TeamID: e.TeamID.ValueString(), EnvID: e.ID.ValueString(), ProjectIDs: projectIDs, diff --git a/vercel/resource_shared_environment_variable_test.go b/vercel/resource_shared_environment_variable_test.go index 08dbd647..953a00a7 100644 --- a/vercel/resource_shared_environment_variable_test.go +++ b/vercel/resource_shared_environment_variable_test.go @@ -74,12 +74,28 @@ func TestAcc_SharedEnvironmentVariables(t *testing.T) { resource.TestCheckTypeSetElemAttr("vercel_shared_environment_variable.example", "target.*", "preview"), ), }, + { + Config: testAccSharedEnvironmentVariablesConfigUpdated(nameSuffix), + Check: resource.ComposeAggregateTestCheckFunc( + testAccProjectEnvironmentVariableExists("vercel_project_environment_variable.example_sensitive", testTeam()), + resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "key", "foo"), + resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "value", "bar-production"), + resource.TestCheckTypeSetElemAttr("vercel_project_environment_variable.example_sensitive", "target.*", "production"), + resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "sensitive", "true"), + ), + }, { ResourceName: "vercel_shared_environment_variable.example", ImportState: true, ImportStateVerify: true, ImportStateIdFunc: getSharedEnvironmentVariableImportID("vercel_shared_environment_variable.example"), }, + { + ResourceName: "vercel_project_environment_variable.example_sensitive", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: getProjectEnvironmentVariableImportID("vercel_project_environment_variable.example_sensitive"), + }, { Config: testAccSharedEnvironmentVariablesConfigDeleted(nameSuffix), Check: resource.ComposeAggregateTestCheckFunc( From 274c16e0fea152051d00a54fba19c2b9289c4300 Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Mon, 29 Jan 2024 15:10:07 +0000 Subject: [PATCH 2/7] Fix tests, mark sensitive field as computed - This means the field will correctly be shown as a computed value if not specified - We set a value of computed on state for every API response. --- vercel/resource_project.go | 7 +++---- vercel/resource_project_environment_variable.go | 1 + vercel/resource_project_environment_variable_model.go | 1 + vercel/resource_project_model.go | 5 ++++- vercel/resource_shared_environment_variable.go | 1 + vercel/resource_shared_environment_variable_model.go | 1 + 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/vercel/resource_project.go b/vercel/resource_project.go index e095e3fc..079017df 100644 --- a/vercel/resource_project.go +++ b/vercel/resource_project.go @@ -9,7 +9,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" @@ -141,9 +140,9 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ Computed: true, }, "sensitive": schema.BoolAttribute{ - Description: "Whether the Environment Variable is sensitive or not.", - Optional: true, - PlanModifiers: []planmodifier.Bool{boolplanmodifier.RequiresReplace()}, + Description: "Whether the Environment Variable is sensitive or not.", + Optional: true, + Computed: true, }, }, }, diff --git a/vercel/resource_project_environment_variable.go b/vercel/resource_project_environment_variable.go index 8cbc371b..b0c6088e 100644 --- a/vercel/resource_project_environment_variable.go +++ b/vercel/resource_project_environment_variable.go @@ -107,6 +107,7 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ "sensitive": schema.BoolAttribute{ Description: "Whether the Environment Variable is sensitive or not.", Optional: true, + Computed: true, PlanModifiers: []planmodifier.Bool{boolplanmodifier.RequiresReplace()}, }, }, diff --git a/vercel/resource_project_environment_variable_model.go b/vercel/resource_project_environment_variable_model.go index 37966a55..00c24c5f 100644 --- a/vercel/resource_project_environment_variable_model.go +++ b/vercel/resource_project_environment_variable_model.go @@ -86,5 +86,6 @@ func convertResponseToProjectEnvironmentVariable(response client.EnvironmentVari TeamID: toTeamID(response.TeamID), ProjectID: projectID, ID: types.StringValue(response.ID), + Sensitive: types.BoolValue(response.Type == "sensitive"), } } diff --git a/vercel/resource_project_model.go b/vercel/resource_project_model.go index a776a0d4..185a3606 100644 --- a/vercel/resource_project_model.go +++ b/vercel/resource_project_model.go @@ -321,6 +321,7 @@ var envVariableElemType = types.ObjectType{ }, "git_branch": types.StringType, "id": types.StringType, + "sensitive": types.BoolType, }, } @@ -351,7 +352,7 @@ func convertResponseToProject(response client.ProjectResponse, plan Project) Pro } } - var va *VercelAuthentication = &VercelAuthentication{ + var va = &VercelAuthentication{ DeploymentType: types.StringValue("none"), } if response.VercelAuthentication != nil { @@ -391,6 +392,7 @@ func convertResponseToProject(response client.ProjectResponse, plan Project) Pro }, "git_branch": types.StringType, "id": types.StringType, + "sensitive": types.BoolType, }, map[string]attr.Value{ "key": types.StringValue(e.Key), @@ -398,6 +400,7 @@ func convertResponseToProject(response client.ProjectResponse, plan Project) Pro "target": types.SetValueMust(types.StringType, target), "git_branch": fromStringPointer(e.GitBranch), "id": types.StringValue(e.ID), + "sensitive": types.BoolValue(e.Type == "sensitive"), }, )) } diff --git a/vercel/resource_shared_environment_variable.go b/vercel/resource_shared_environment_variable.go index 8b8f61df..3715078b 100644 --- a/vercel/resource_shared_environment_variable.go +++ b/vercel/resource_shared_environment_variable.go @@ -100,6 +100,7 @@ For more detailed information, please see the [Vercel documentation](https://ver "sensitive": schema.BoolAttribute{ Description: "Whether the Environment Variable is sensitive or not.", Optional: true, + Computed: true, PlanModifiers: []planmodifier.Bool{boolplanmodifier.RequiresReplace()}, }, }, diff --git a/vercel/resource_shared_environment_variable_model.go b/vercel/resource_shared_environment_variable_model.go index 4705a6a3..e8d2c8db 100644 --- a/vercel/resource_shared_environment_variable_model.go +++ b/vercel/resource_shared_environment_variable_model.go @@ -98,5 +98,6 @@ func convertResponseToSharedEnvironmentVariable(response client.SharedEnvironmen ProjectIDs: project_ids, TeamID: toTeamID(response.TeamID), ID: types.StringValue(response.ID), + Sensitive: types.BoolValue(response.Type == "sensitive"), } } From 67609b917331b564d81cc45f81bfce49e51fcccb Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Tue, 30 Jan 2024 12:19:13 +0000 Subject: [PATCH 3/7] Fixes for sensitive env vars - Make it disregard the Read command for sensitive env vars - Fix the sweep script, and make shared env vars use unique keys - Add a client error for detecting when a value can't be decrypted. - Fix Project Data Source type mismatches --- client/error.go | 6 ++ client/shared_environment_variable_list.go | 32 ++++++++++ client/shared_environment_variable_update.go | 1 - sweep/main.go | 26 +++++++- vercel/data_source_project.go | 4 ++ ...ource_project_environment_variable_test.go | 18 ++++-- .../resource_shared_environment_variable.go | 3 + ...ource_shared_environment_variable_model.go | 1 - ...source_shared_environment_variable_test.go | 59 ++++++++++++------- 9 files changed, 118 insertions(+), 32 deletions(-) create mode 100644 client/shared_environment_variable_list.go diff --git a/client/error.go b/client/error.go index 688fdcb3..a7a82125 100644 --- a/client/error.go +++ b/client/error.go @@ -7,3 +7,9 @@ func NotFound(err error) bool { var apiErr APIError return err != nil && errors.As(err, &apiErr) && apiErr.StatusCode == 404 } + +// NotFound detects if an error returned by the Vercel API was the result of a sensitive env var not being able to be decrypted +func NotDecryptable(err error) bool { + var apiErr APIError + return err != nil && errors.As(err, &apiErr) && apiErr.Code == "not_decryptable" +} diff --git a/client/shared_environment_variable_list.go b/client/shared_environment_variable_list.go new file mode 100644 index 00000000..984f3a2b --- /dev/null +++ b/client/shared_environment_variable_list.go @@ -0,0 +1,32 @@ +package client + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +func (c *Client) ListSharedEnvironmentVariables(ctx context.Context, teamID string) ([]SharedEnvironmentVariableResponse, error) { + url := fmt.Sprintf("%s/v1/env/all", c.baseURL) + if c.teamID(teamID) != "" { + url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(teamID)) + } + + tflog.Trace(ctx, "listing shared environment variables", map[string]interface{}{ + "url": url, + }) + res := struct { + Data []SharedEnvironmentVariableResponse `json:"data"` + }{} + err := c.doRequest(clientRequest{ + ctx: ctx, + method: "GET", + url: url, + body: "", + }, &res) + for _, v := range res.Data { + v.TeamID = c.teamID(teamID) + } + return res.Data, err +} diff --git a/client/shared_environment_variable_update.go b/client/shared_environment_variable_update.go index f6bb73ce..693cf32f 100644 --- a/client/shared_environment_variable_update.go +++ b/client/shared_environment_variable_update.go @@ -8,7 +8,6 @@ import ( ) type UpdateSharedEnvironmentVariableRequest struct { - Key string `json:"key"` Value string `json:"value"` Type string `json:"type"` ProjectIDs []string `json:"projectId"` diff --git a/sweep/main.go b/sweep/main.go index 690ee8e3..ebf48637 100644 --- a/sweep/main.go +++ b/sweep/main.go @@ -31,14 +31,34 @@ func main() { if err != nil { panic(err) } - err = deleteAllProjects(ctx, c, "") + // err = deleteAllDNSRecords(ctx, c, domain, teamID) + // if err != nil { + // panic(err) + // } + err = deleteAllSharedEnvironmentVariables(ctx, c, teamID) if err != nil { panic(err) } - err = deleteAllDNSRecords(ctx, c, domain, "") +} + +func deleteAllSharedEnvironmentVariables(ctx context.Context, c *client.Client, teamID string) error { + sharedEnvironmentVariables, err := c.ListSharedEnvironmentVariables(ctx, teamID) if err != nil { - panic(err) + return fmt.Errorf("error listing shared environment variables: %w", err) } + for _, d := range sharedEnvironmentVariables { + if !strings.HasPrefix(d.Key, "test_acc") { + // Don't delete actual shared environment variables - only testing ones + continue + } + + err = c.DeleteSharedEnvironmentVariable(ctx, teamID, d.ID) + if err != nil { + return fmt.Errorf("error deleting shared env var %s: %w", d.Key, err) + } + } + + return nil } func deleteAllDNSRecords(ctx context.Context, c *client.Client, domain, teamID string) error { diff --git a/vercel/data_source_project.go b/vercel/data_source_project.go index ff92a180..f0f84e6f 100644 --- a/vercel/data_source_project.go +++ b/vercel/data_source_project.go @@ -118,6 +118,10 @@ For more detailed information, please see the [Vercel documentation](https://ver Description: "The git branch of the environment variable.", Computed: true, }, + "sensitive": schema.BoolAttribute{ + Description: "Whether the Environment Variable is sensitive or not.", + Computed: true, + }, }, }, }, diff --git a/vercel/resource_project_environment_variable_test.go b/vercel/resource_project_environment_variable_test.go index 07731094..1145001a 100644 --- a/vercel/resource_project_environment_variable_test.go +++ b/vercel/resource_project_environment_variable_test.go @@ -74,8 +74,8 @@ func TestAcc_ProjectEnvironmentVariables(t *testing.T) { resource.TestCheckResourceAttr("vercel_project_environment_variable.example_git_branch", "git_branch", "production"), testAccProjectEnvironmentVariableExists("vercel_project_environment_variable.example_sensitive", testTeam()), - resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "key", "foo"), - resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "value", "bar-production"), + resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "key", "foo_sensitive"), + resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "value", "bar-sensitive"), resource.TestCheckTypeSetElemAttr("vercel_project_environment_variable.example_sensitive", "target.*", "production"), resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "sensitive", "true"), ), @@ -94,6 +94,12 @@ func TestAcc_ProjectEnvironmentVariables(t *testing.T) { resource.TestCheckResourceAttr("vercel_project_environment_variable.example_git_branch", "value", "bar-staging"), resource.TestCheckTypeSetElemAttr("vercel_project_environment_variable.example_git_branch", "target.*", "preview"), resource.TestCheckResourceAttr("vercel_project_environment_variable.example_git_branch", "git_branch", "test"), + + testAccProjectEnvironmentVariableExists("vercel_project_environment_variable.example_sensitive", testTeam()), + resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "key", "foo_sensitive_updated"), + resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "value", "bar-sensitive-updated"), + resource.TestCheckTypeSetElemAttr("vercel_project_environment_variable.example_sensitive", "target.*", "production"), + resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "sensitive", "true"), ), }, { @@ -174,8 +180,8 @@ resource "vercel_project_environment_variable" "example_git_branch" { resource "vercel_project_environment_variable" "example_sensitive" { project_id = vercel_project.example.id %[3]s - key = "foo" - value = "bar-production" + key = "foo_sensitive" + value = "bar-sensitive" target = ["production"] sensitive = true } @@ -214,8 +220,8 @@ resource "vercel_project_environment_variable" "example_git_branch" { resource "vercel_project_environment_variable" "example_sensitive" { project_id = vercel_project.example.id %[3]s - key = "foo-2" - value = "bar-production-2" + key = "foo_sensitive_updated" + value = "bar-sensitive-updated" target = ["production"] sensitive = true } diff --git a/vercel/resource_shared_environment_variable.go b/vercel/resource_shared_environment_variable.go index 3715078b..1480b0da 100644 --- a/vercel/resource_shared_environment_variable.go +++ b/vercel/resource_shared_environment_variable.go @@ -155,6 +155,9 @@ func (r *sharedEnvironmentVariableResource) Read(ctx context.Context, req resour resp.State.RemoveResource(ctx) return } + if client.NotDecryptable(err) { + return + } if err != nil { resp.Diagnostics.AddError( "Error reading shared environment variable", diff --git a/vercel/resource_shared_environment_variable_model.go b/vercel/resource_shared_environment_variable_model.go index e8d2c8db..2083d621 100644 --- a/vercel/resource_shared_environment_variable_model.go +++ b/vercel/resource_shared_environment_variable_model.go @@ -67,7 +67,6 @@ func (e *SharedEnvironmentVariable) toUpdateSharedEnvironmentVariableRequest() c envVariableType = "encrypted" } return client.UpdateSharedEnvironmentVariableRequest{ - Key: e.Key.ValueString(), Value: e.Value.ValueString(), Target: target, Type: envVariableType, diff --git a/vercel/resource_shared_environment_variable_test.go b/vercel/resource_shared_environment_variable_test.go index 953a00a7..783912e0 100644 --- a/vercel/resource_shared_environment_variable_test.go +++ b/vercel/resource_shared_environment_variable_test.go @@ -48,6 +48,7 @@ func testAccSharedEnvironmentVariableDoesNotExist(n, teamID string) resource.Tes func TestAcc_SharedEnvironmentVariables(t *testing.T) { nameSuffix := acctest.RandString(16) + fmt.Println("NAME SUFFIX", nameSuffix) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, @@ -59,29 +60,27 @@ func TestAcc_SharedEnvironmentVariables(t *testing.T) { Config: testAccSharedEnvironmentVariablesConfig(nameSuffix), Check: resource.ComposeAggregateTestCheckFunc( testAccSharedEnvironmentVariableExists("vercel_shared_environment_variable.example", testTeam()), - resource.TestCheckResourceAttr("vercel_shared_environment_variable.example", "key", "foo"), + resource.TestCheckResourceAttr("vercel_shared_environment_variable.example", "key", fmt.Sprintf("test_acc_foo_%s", nameSuffix)), resource.TestCheckResourceAttr("vercel_shared_environment_variable.example", "value", "bar"), resource.TestCheckTypeSetElemAttr("vercel_shared_environment_variable.example", "target.*", "production"), + + resource.TestCheckResourceAttr("vercel_shared_environment_variable.sensitive_example", "key", fmt.Sprintf("test_acc_foo_sensitive_%s", nameSuffix)), + resource.TestCheckResourceAttr("vercel_shared_environment_variable.sensitive_example", "value", "bar"), + resource.TestCheckTypeSetElemAttr("vercel_shared_environment_variable.sensitive_example", "target.*", "production"), ), }, { Config: testAccSharedEnvironmentVariablesConfigUpdated(nameSuffix), Check: resource.ComposeAggregateTestCheckFunc( testAccSharedEnvironmentVariableExists("vercel_shared_environment_variable.example", testTeam()), - resource.TestCheckResourceAttr("vercel_shared_environment_variable.example", "key", "foo"), + resource.TestCheckResourceAttr("vercel_shared_environment_variable.example", "key", fmt.Sprintf("test_acc_foo_%s", nameSuffix)), resource.TestCheckResourceAttr("vercel_shared_environment_variable.example", "value", "updated-bar"), resource.TestCheckTypeSetElemAttr("vercel_shared_environment_variable.example", "target.*", "development"), resource.TestCheckTypeSetElemAttr("vercel_shared_environment_variable.example", "target.*", "preview"), - ), - }, - { - Config: testAccSharedEnvironmentVariablesConfigUpdated(nameSuffix), - Check: resource.ComposeAggregateTestCheckFunc( - testAccProjectEnvironmentVariableExists("vercel_project_environment_variable.example_sensitive", testTeam()), - resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "key", "foo"), - resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "value", "bar-production"), - resource.TestCheckTypeSetElemAttr("vercel_project_environment_variable.example_sensitive", "target.*", "production"), - resource.TestCheckResourceAttr("vercel_project_environment_variable.example_sensitive", "sensitive", "true"), + + resource.TestCheckResourceAttr("vercel_shared_environment_variable.sensitive_example", "key", fmt.Sprintf("test_acc_foo_sensitive_%s", nameSuffix)), + resource.TestCheckResourceAttr("vercel_shared_environment_variable.sensitive_example", "value", "bar-updated"), + resource.TestCheckTypeSetElemAttr("vercel_shared_environment_variable.sensitive_example", "target.*", "production"), ), }, { @@ -90,16 +89,11 @@ func TestAcc_SharedEnvironmentVariables(t *testing.T) { ImportStateVerify: true, ImportStateIdFunc: getSharedEnvironmentVariableImportID("vercel_shared_environment_variable.example"), }, - { - ResourceName: "vercel_project_environment_variable.example_sensitive", - ImportState: true, - ImportStateVerify: true, - ImportStateIdFunc: getProjectEnvironmentVariableImportID("vercel_project_environment_variable.example_sensitive"), - }, { Config: testAccSharedEnvironmentVariablesConfigDeleted(nameSuffix), Check: resource.ComposeAggregateTestCheckFunc( - testAccSharedEnvironmentVariableDoesNotExist("vercel_project.example", testTeam()), + testAccSharedEnvironmentVariableDoesNotExist("vercel_shared_environment_variable.example", testTeam()), + testAccSharedEnvironmentVariableDoesNotExist("vercel_shared_environment_variable.sensitive_example", testTeam()), ), }, }, @@ -130,13 +124,24 @@ resource "vercel_project" "example" { resource "vercel_shared_environment_variable" "example" { %[2]s - key = "foo" + key = "test_acc_foo_%[1]s" value = "bar" target = ["production"] project_ids = [ vercel_project.example.id ] } + +resource "vercel_shared_environment_variable" "sensitive_example" { + %[2]s + key = "test_acc_foo_sensitive_%[1]s" + value = "bar" + sensitive = true + target = ["production"] + project_ids = [ + vercel_project.example.id + ] +} `, projectName, teamIDConfig()) } @@ -154,7 +159,7 @@ resource "vercel_project" "example2" { resource "vercel_shared_environment_variable" "example" { %[2]s - key = "foo" + key = "test_acc_foo_%[1]s" value = "updated-bar" target = ["preview", "development"] project_ids = [ @@ -162,6 +167,18 @@ resource "vercel_shared_environment_variable" "example" { vercel_project.example2.id ] } + +resource "vercel_shared_environment_variable" "sensitive_example" { + %[2]s + key = "test_acc_foo_sensitive_%[1]s" + value = "bar-updated" + sensitive = true + target = ["production"] + project_ids = [ + vercel_project.example.id, + vercel_project.example2.id + ] +} `, projectName, teamIDConfig()) } From c69cf687a7a2280d8f5e7f9a4cec3401898eb822 Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Mon, 5 Feb 2024 13:06:47 +0000 Subject: [PATCH 4/7] Remove 403 workaround + fix shared_environment_variable tests --- client/error.go | 6 ----- .../resource_shared_environment_variable.go | 11 +++----- ...ource_shared_environment_variable_model.go | 9 +++++-- ...source_shared_environment_variable_test.go | 25 ------------------- 4 files changed, 11 insertions(+), 40 deletions(-) diff --git a/client/error.go b/client/error.go index a7a82125..688fdcb3 100644 --- a/client/error.go +++ b/client/error.go @@ -7,9 +7,3 @@ func NotFound(err error) bool { var apiErr APIError return err != nil && errors.As(err, &apiErr) && apiErr.StatusCode == 404 } - -// NotFound detects if an error returned by the Vercel API was the result of a sensitive env var not being able to be decrypted -func NotDecryptable(err error) bool { - var apiErr APIError - return err != nil && errors.As(err, &apiErr) && apiErr.Code == "not_decryptable" -} diff --git a/vercel/resource_shared_environment_variable.go b/vercel/resource_shared_environment_variable.go index 1480b0da..6c045753 100644 --- a/vercel/resource_shared_environment_variable.go +++ b/vercel/resource_shared_environment_variable.go @@ -126,7 +126,7 @@ func (r *sharedEnvironmentVariableResource) Create(ctx context.Context, req reso return } - result := convertResponseToSharedEnvironmentVariable(response) + result := convertResponseToSharedEnvironmentVariable(response, plan.Value) tflog.Trace(ctx, "created shared environment variable", map[string]interface{}{ "id": result.ID.ValueString(), @@ -155,9 +155,6 @@ func (r *sharedEnvironmentVariableResource) Read(ctx context.Context, req resour resp.State.RemoveResource(ctx) return } - if client.NotDecryptable(err) { - return - } if err != nil { resp.Diagnostics.AddError( "Error reading shared environment variable", @@ -170,7 +167,7 @@ func (r *sharedEnvironmentVariableResource) Read(ctx context.Context, req resour return } - result := convertResponseToSharedEnvironmentVariable(out) + result := convertResponseToSharedEnvironmentVariable(out, state.Value) tflog.Trace(ctx, "read shared environment variable", map[string]interface{}{ "id": result.ID.ValueString(), "team_id": result.TeamID.ValueString(), @@ -201,7 +198,7 @@ func (r *sharedEnvironmentVariableResource) Update(ctx context.Context, req reso return } - result := convertResponseToSharedEnvironmentVariable(response) + result := convertResponseToSharedEnvironmentVariable(response, plan.Value) tflog.Trace(ctx, "updated project environment variable", map[string]interface{}{ "id": result.ID.ValueString(), @@ -281,7 +278,7 @@ func (r *sharedEnvironmentVariableResource) ImportState(ctx context.Context, req return } - result := convertResponseToSharedEnvironmentVariable(out) + result := convertResponseToSharedEnvironmentVariable(out, types.StringNull()) tflog.Trace(ctx, "imported shared environment variable", map[string]interface{}{ "team_id": result.TeamID.ValueString(), "env_id": result.ID.ValueString(), diff --git a/vercel/resource_shared_environment_variable_model.go b/vercel/resource_shared_environment_variable_model.go index 2083d621..83bb83b7 100644 --- a/vercel/resource_shared_environment_variable_model.go +++ b/vercel/resource_shared_environment_variable_model.go @@ -79,7 +79,7 @@ func (e *SharedEnvironmentVariable) toUpdateSharedEnvironmentVariableRequest() c // convertResponseToSharedEnvironmentVariable is used to populate terraform state based on an API response. // Where possible, values from the API response are used to populate state. If not possible, // values from plan are used. -func convertResponseToSharedEnvironmentVariable(response client.SharedEnvironmentVariableResponse) SharedEnvironmentVariable { +func convertResponseToSharedEnvironmentVariable(response client.SharedEnvironmentVariableResponse, v types.String) SharedEnvironmentVariable { target := []types.String{} for _, t := range response.Target { target = append(target, types.StringValue(t)) @@ -90,10 +90,15 @@ func convertResponseToSharedEnvironmentVariable(response client.SharedEnvironmen project_ids = append(project_ids, types.StringValue(t)) } + value := types.StringValue(response.Value) + if response.Type == "sensitive" { + value = v + } + return SharedEnvironmentVariable{ Target: target, Key: types.StringValue(response.Key), - Value: types.StringValue(response.Value), + Value: value, ProjectIDs: project_ids, TeamID: toTeamID(response.TeamID), ID: types.StringValue(response.ID), diff --git a/vercel/resource_shared_environment_variable_test.go b/vercel/resource_shared_environment_variable_test.go index 783912e0..2b022f20 100644 --- a/vercel/resource_shared_environment_variable_test.go +++ b/vercel/resource_shared_environment_variable_test.go @@ -26,29 +26,8 @@ func testAccSharedEnvironmentVariableExists(n, teamID string) resource.TestCheck } } -func testAccSharedEnvironmentVariableDoesNotExist(n, teamID string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("no ID is set") - } - - _, err := testClient().GetSharedEnvironmentVariable(context.TODO(), teamID, rs.Primary.ID) - - if err != nil { - return nil - } - return fmt.Errorf("expected an error, but got none") - } -} - func TestAcc_SharedEnvironmentVariables(t *testing.T) { nameSuffix := acctest.RandString(16) - fmt.Println("NAME SUFFIX", nameSuffix) resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, @@ -91,10 +70,6 @@ func TestAcc_SharedEnvironmentVariables(t *testing.T) { }, { Config: testAccSharedEnvironmentVariablesConfigDeleted(nameSuffix), - Check: resource.ComposeAggregateTestCheckFunc( - testAccSharedEnvironmentVariableDoesNotExist("vercel_shared_environment_variable.example", testTeam()), - testAccSharedEnvironmentVariableDoesNotExist("vercel_shared_environment_variable.sensitive_example", testTeam()), - ), }, }, }) From f4b8020651cc2dab7cbd34965de936565d66d856 Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Mon, 5 Feb 2024 13:30:29 +0000 Subject: [PATCH 5/7] Fix project environment variable tests --- vercel/resource_project_environment_variable.go | 8 ++++---- vercel/resource_project_environment_variable_model.go | 9 +++++++-- vercel/resource_project_environment_variable_test.go | 6 ------ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/vercel/resource_project_environment_variable.go b/vercel/resource_project_environment_variable.go index b0c6088e..731a5899 100644 --- a/vercel/resource_project_environment_variable.go +++ b/vercel/resource_project_environment_variable.go @@ -142,7 +142,7 @@ func (r *projectEnvironmentVariableResource) Create(ctx context.Context, req res return } - result := convertResponseToProjectEnvironmentVariable(response, plan.ProjectID) + result := convertResponseToProjectEnvironmentVariable(response, plan.ProjectID, plan.Value) tflog.Trace(ctx, "created project environment variable", map[string]interface{}{ "id": result.ID.ValueString(), @@ -185,7 +185,7 @@ func (r *projectEnvironmentVariableResource) Read(ctx context.Context, req resou return } - result := convertResponseToProjectEnvironmentVariable(out, state.ProjectID) + result := convertResponseToProjectEnvironmentVariable(out, state.ProjectID, state.Value) tflog.Trace(ctx, "read project environment variable", map[string]interface{}{ "id": result.ID.ValueString(), "team_id": result.TeamID.ValueString(), @@ -217,7 +217,7 @@ func (r *projectEnvironmentVariableResource) Update(ctx context.Context, req res return } - result := convertResponseToProjectEnvironmentVariable(response, plan.ProjectID) + result := convertResponseToProjectEnvironmentVariable(response, plan.ProjectID, plan.Value) tflog.Trace(ctx, "updated project environment variable", map[string]interface{}{ "id": result.ID.ValueString(), @@ -303,7 +303,7 @@ func (r *projectEnvironmentVariableResource) ImportState(ctx context.Context, re return } - result := convertResponseToProjectEnvironmentVariable(out, types.StringValue(projectID)) + result := convertResponseToProjectEnvironmentVariable(out, types.StringValue(projectID), types.StringNull()) tflog.Trace(ctx, "imported project environment variable", map[string]interface{}{ "team_id": result.TeamID.ValueString(), "project_id": result.ProjectID.ValueString(), diff --git a/vercel/resource_project_environment_variable_model.go b/vercel/resource_project_environment_variable_model.go index 00c24c5f..dfa1cb8e 100644 --- a/vercel/resource_project_environment_variable_model.go +++ b/vercel/resource_project_environment_variable_model.go @@ -72,17 +72,22 @@ func (e *ProjectEnvironmentVariable) toUpdateEnvironmentVariableRequest() client // convertResponseToProjectEnvironmentVariable is used to populate terraform state based on an API response. // Where possible, values from the API response are used to populate state. If not possible, // values from plan are used. -func convertResponseToProjectEnvironmentVariable(response client.EnvironmentVariable, projectID types.String) ProjectEnvironmentVariable { +func convertResponseToProjectEnvironmentVariable(response client.EnvironmentVariable, projectID types.String, v types.String) ProjectEnvironmentVariable { target := []types.String{} for _, t := range response.Target { target = append(target, types.StringValue(t)) } + value := types.StringValue(response.Value) + if response.Type == "sensitive" { + value = v + } + return ProjectEnvironmentVariable{ Target: target, GitBranch: fromStringPointer(response.GitBranch), Key: types.StringValue(response.Key), - Value: types.StringValue(response.Value), + Value: value, TeamID: toTeamID(response.TeamID), ProjectID: projectID, ID: types.StringValue(response.ID), diff --git a/vercel/resource_project_environment_variable_test.go b/vercel/resource_project_environment_variable_test.go index 1145001a..2959a734 100644 --- a/vercel/resource_project_environment_variable_test.go +++ b/vercel/resource_project_environment_variable_test.go @@ -114,12 +114,6 @@ func TestAcc_ProjectEnvironmentVariables(t *testing.T) { ImportStateVerify: true, ImportStateIdFunc: getProjectEnvironmentVariableImportID("vercel_project_environment_variable.example_git_branch"), }, - { - ResourceName: "vercel_project_environment_variable.example_sensitive", - ImportState: true, - ImportStateVerify: true, - ImportStateIdFunc: getProjectEnvironmentVariableImportID("vercel_project_environment_variable.example_sensitive"), - }, { Config: testAccProjectEnvironmentVariablesConfigDeleted(nameSuffix), Check: resource.ComposeAggregateTestCheckFunc( From ed7fd31d3fd7d110d1da5697aa1755da7a223bb1 Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Tue, 6 Feb 2024 09:14:06 +0000 Subject: [PATCH 6/7] Fix project + docs --- docs/data-sources/project.md | 1 + .../resources/project_environment_variable.md | 4 ++ docs/resources/shared_environment_variable.md | 2 + .../import.sh | 4 ++ .../import.sh | 2 + sweep/main.go | 8 +-- vercel/contains.go | 10 ++++ vercel/data_source_project.go | 11 +++- vercel/data_source_project_model.go | 11 ++-- vercel/resource_project.go | 54 ++++++++++++++++--- vercel/resource_project_model.go | 34 ++++++++++-- vercel/resource_project_test.go | 12 +++++ .../validator_serverless_function_region.go | 9 ---- 13 files changed, 135 insertions(+), 27 deletions(-) create mode 100644 vercel/contains.go diff --git a/docs/data-sources/project.md b/docs/data-sources/project.md index c7dbec3c..fc252626 100644 --- a/docs/data-sources/project.md +++ b/docs/data-sources/project.md @@ -66,6 +66,7 @@ Read-Only: - `git_branch` (String) The git branch of the environment variable. - `id` (String) The ID of the environment variable - `key` (String) The name of the environment variable. +- `sensitive` (Boolean) Whether the Environment Variable is sensitive or not. Note that the value will be `null` for sensitive environment variables. - `target` (Set of String) The environments that the environment variable should be present on. Valid targets are either `production`, `preview`, or `development`. - `value` (String) The value of the environment variable. diff --git a/docs/resources/project_environment_variable.md b/docs/resources/project_environment_variable.md index a83f0340..7a0fb088 100644 --- a/docs/resources/project_environment_variable.md +++ b/docs/resources/project_environment_variable.md @@ -92,6 +92,8 @@ Import is supported using the following syntax: # the provider, simply use the project_id and environment variable id. # - project_id can be found in the project `settings` tab in the Vercel UI. # - environment variable id is hard to find, but can be taken from the network tab, inside developer tools, on the project page. +# +# Note also, that the value field for sensitive environment variables will be imported as `null`. terraform import vercel_project_environment_variable.example prj_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/FdT2e1E5Of6Cihmt # Alternatively, you can import via the team_id, project_id and @@ -99,5 +101,7 @@ terraform import vercel_project_environment_variable.example prj_xxxxxxxxxxxxxxx # - team_id can be found in the team `settings` tab in the Vercel UI. # - project_id can be found in the project `settings` tab in the Vercel UI. # - environment variable id is hard to find, but can be taken from the network tab, inside developer tools, on the project page. +# +# Note also, that the value field for sensitive environment variables will be imported as `null`. terraform import vercel_project_environment_variable.example team_xxxxxxxxxxxxxxxxxxxxxxxx/prj_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/FdT2e1E5Of6Cihmt ``` diff --git a/docs/resources/shared_environment_variable.md b/docs/resources/shared_environment_variable.md index aa0d49ad..c571aa6d 100644 --- a/docs/resources/shared_environment_variable.md +++ b/docs/resources/shared_environment_variable.md @@ -67,5 +67,7 @@ Import is supported using the following syntax: # You can import via the team_id and environment variable id. # - team_id can be found in the team `settings` tab in the Vercel UI. # - environment variable id is hard to find, but can be taken from the network tab, inside developer tools, on the shared environment variable page. +# +# Note also, that the value field for sensitive environment variables will be imported as `null`. terraform import vercel_shared_environment_variable.example team_xxxxxxxxxxxxxxxxxxxxxxxx/env_yyyyyyyyyyyyy ``` diff --git a/examples/resources/vercel_project_environment_variable/import.sh b/examples/resources/vercel_project_environment_variable/import.sh index 2452b79d..d7f26c63 100644 --- a/examples/resources/vercel_project_environment_variable/import.sh +++ b/examples/resources/vercel_project_environment_variable/import.sh @@ -2,6 +2,8 @@ # the provider, simply use the project_id and environment variable id. # - project_id can be found in the project `settings` tab in the Vercel UI. # - environment variable id is hard to find, but can be taken from the network tab, inside developer tools, on the project page. +# +# Note also, that the value field for sensitive environment variables will be imported as `null`. terraform import vercel_project_environment_variable.example prj_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/FdT2e1E5Of6Cihmt # Alternatively, you can import via the team_id, project_id and @@ -9,4 +11,6 @@ terraform import vercel_project_environment_variable.example prj_xxxxxxxxxxxxxxx # - team_id can be found in the team `settings` tab in the Vercel UI. # - project_id can be found in the project `settings` tab in the Vercel UI. # - environment variable id is hard to find, but can be taken from the network tab, inside developer tools, on the project page. +# +# Note also, that the value field for sensitive environment variables will be imported as `null`. terraform import vercel_project_environment_variable.example team_xxxxxxxxxxxxxxxxxxxxxxxx/prj_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/FdT2e1E5Of6Cihmt diff --git a/examples/resources/vercel_shared_environment_variable/import.sh b/examples/resources/vercel_shared_environment_variable/import.sh index 5eb4aa09..0dcf59fd 100644 --- a/examples/resources/vercel_shared_environment_variable/import.sh +++ b/examples/resources/vercel_shared_environment_variable/import.sh @@ -1,4 +1,6 @@ # You can import via the team_id and environment variable id. # - team_id can be found in the team `settings` tab in the Vercel UI. # - environment variable id is hard to find, but can be taken from the network tab, inside developer tools, on the shared environment variable page. +# +# Note also, that the value field for sensitive environment variables will be imported as `null`. terraform import vercel_shared_environment_variable.example team_xxxxxxxxxxxxxxxxxxxxxxxx/env_yyyyyyyyyyyyy diff --git a/sweep/main.go b/sweep/main.go index ebf48637..e1697d43 100644 --- a/sweep/main.go +++ b/sweep/main.go @@ -31,10 +31,10 @@ func main() { if err != nil { panic(err) } - // err = deleteAllDNSRecords(ctx, c, domain, teamID) - // if err != nil { - // panic(err) - // } + err = deleteAllDNSRecords(ctx, c, domain, teamID) + if err != nil { + panic(err) + } err = deleteAllSharedEnvironmentVariables(ctx, c, teamID) if err != nil { panic(err) diff --git a/vercel/contains.go b/vercel/contains.go new file mode 100644 index 00000000..3fe39aeb --- /dev/null +++ b/vercel/contains.go @@ -0,0 +1,10 @@ +package vercel + +func contains(items []string, i string) bool { + for _, j := range items { + if j == i { + return true + } + } + return false +} diff --git a/vercel/data_source_project.go b/vercel/data_source_project.go index f0f84e6f..9aa12213 100644 --- a/vercel/data_source_project.go +++ b/vercel/data_source_project.go @@ -119,7 +119,7 @@ For more detailed information, please see the [Vercel documentation](https://ver Computed: true, }, "sensitive": schema.BoolAttribute{ - Description: "Whether the Environment Variable is sensitive or not.", + Description: "Whether the Environment Variable is sensitive or not. Note that the value will be `null` for sensitive environment variables.", Computed: true, }, }, @@ -241,7 +241,14 @@ func (d *projectDataSource) Read(ctx context.Context, req datasource.ReadRequest return } - result := convertResponseToProjectDataSource(out, nullProject) + result, err := convertResponseToProjectDataSource(ctx, out, nullProject) + if err != nil { + resp.Diagnostics.AddError( + "Error converting project response to model", + "Could not create project, unexpected error: "+err.Error(), + ) + return + } tflog.Trace(ctx, "read project", map[string]interface{}{ "team_id": result.TeamID.ValueString(), "project_id": result.ID.ValueString(), diff --git a/vercel/data_source_project_model.go b/vercel/data_source_project_model.go index 6e2d780f..c8bce558 100644 --- a/vercel/data_source_project_model.go +++ b/vercel/data_source_project_model.go @@ -1,6 +1,8 @@ package vercel import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/vercel/terraform-provider-vercel/client" ) @@ -26,8 +28,11 @@ type ProjectDataSource struct { TrustedIps *TrustedIps `tfsdk:"trusted_ips"` } -func convertResponseToProjectDataSource(response client.ProjectResponse, plan Project) ProjectDataSource { - project := convertResponseToProject(response, plan) +func convertResponseToProjectDataSource(ctx context.Context, response client.ProjectResponse, plan Project) (ProjectDataSource, error) { + project, err := convertResponseToProject(ctx, response, plan) + if err != nil { + return ProjectDataSource{}, err + } var pp *PasswordProtection if project.PasswordProtection != nil { @@ -53,5 +58,5 @@ func convertResponseToProjectDataSource(response client.ProjectResponse, plan Pr VercelAuthentication: project.VercelAuthentication, PasswordProtection: pp, TrustedIps: project.TrustedIps, - } + }, nil } diff --git a/vercel/resource_project.go b/vercel/resource_project.go index 079017df..804f7333 100644 --- a/vercel/resource_project.go +++ b/vercel/resource_project.go @@ -330,7 +330,14 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest return } - result := convertResponseToProject(out, plan) + result, err := convertResponseToProject(ctx, out, plan) + if err != nil { + resp.Diagnostics.AddError( + "Error converting project response to model", + "Could not create project, unexpected error: "+err.Error(), + ) + return + } tflog.Trace(ctx, "created project", map[string]interface{}{ "team_id": result.TeamID.ValueString(), "project_id": result.ID.ValueString(), @@ -351,7 +358,14 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest return } - result = convertResponseToProject(out, plan) + result, err = convertResponseToProject(ctx, out, plan) + if err != nil { + resp.Diagnostics.AddError( + "Error converting project response to model", + "Could not create project, unexpected error: "+err.Error(), + ) + return + } tflog.Trace(ctx, "updated newly created project", map[string]interface{}{ "team_id": result.TeamID.ValueString(), "project_id": result.ID.ValueString(), @@ -402,7 +416,14 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest return } - result = convertResponseToProject(out, plan) + result, err = convertResponseToProject(ctx, out, plan) + if err != nil { + resp.Diagnostics.AddError( + "Error converting project response to model", + "Could not create project, unexpected error: "+err.Error(), + ) + return + } tflog.Trace(ctx, "updated project production branch", map[string]interface{}{ "team_id": result.TeamID.ValueString(), "project_id": result.ID.ValueString(), @@ -442,7 +463,14 @@ func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, re return } - result := convertResponseToProject(out, state) + result, err := convertResponseToProject(ctx, out, state) + if err != nil { + resp.Diagnostics.AddError( + "Error converting project response to model", + "Could not create project, unexpected error: "+err.Error(), + ) + return + } tflog.Trace(ctx, "read project", map[string]interface{}{ "team_id": result.TeamID.ValueString(), "project_id": result.ID.ValueString(), @@ -634,7 +662,14 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest } } - result := convertResponseToProject(out, plan) + result, err := convertResponseToProject(ctx, out, plan) + if err != nil { + resp.Diagnostics.AddError( + "Error converting project response to model", + "Could not create project, unexpected error: "+err.Error(), + ) + return + } tflog.Trace(ctx, "updated project", map[string]interface{}{ "team_id": result.TeamID.ValueString(), "project_id": result.ID.ValueString(), @@ -717,7 +752,14 @@ func (r *projectResource) ImportState(ctx context.Context, req resource.ImportSt return } - result := convertResponseToProject(out, nullProject) + result, err := convertResponseToProject(ctx, out, nullProject) + if err != nil { + resp.Diagnostics.AddError( + "Error converting project response to model", + "Could not create project, unexpected error: "+err.Error(), + ) + return + } tflog.Trace(ctx, "imported project", map[string]interface{}{ "team_id": result.TeamID.ValueString(), "project_id": result.ID.ValueString(), diff --git a/vercel/resource_project_model.go b/vercel/resource_project_model.go index 185a3606..f45e0e2e 100644 --- a/vercel/resource_project_model.go +++ b/vercel/resource_project_model.go @@ -325,7 +325,20 @@ var envVariableElemType = types.ObjectType{ }, } -func convertResponseToProject(response client.ProjectResponse, plan Project) Project { +func hasSameTarget(p EnvironmentItem, target []string) bool { + if len(p.Target) != len(target) { + return false + } + for _, t := range p.Target { + v := t.ValueString() + if !contains(target, v) { + return false + } + } + return true +} + +func convertResponseToProject(ctx context.Context, response client.ProjectResponse, plan Project) (Project, error) { fields := plan.coercedFields() var gr *GitRepository @@ -383,6 +396,21 @@ func convertResponseToProject(response client.ProjectResponse, plan Project) Pro for _, t := range e.Target { target = append(target, types.StringValue(t)) } + value := types.StringValue(e.Value) + if e.Type == "sensitive" { + value = types.StringNull() + environment, err := plan.environment(ctx) + if err != nil { + return Project{}, fmt.Errorf("error reading project environment variables: %s", err) + } + for _, p := range environment { + if p.Sensitive.ValueBool() && p.Key.ValueString() == e.Key && hasSameTarget(p, e.Target) { + value = p.Value + break + } + } + } + env = append(env, types.ObjectValueMust( map[string]attr.Type{ "key": types.StringType, @@ -396,7 +424,7 @@ func convertResponseToProject(response client.ProjectResponse, plan Project) Pro }, map[string]attr.Value{ "key": types.StringValue(e.Key), - "value": types.StringValue(e.Value), + "value": value, "target": types.SetValueMust(types.StringType, target), "git_branch": fromStringPointer(e.GitBranch), "id": types.StringValue(e.ID), @@ -443,5 +471,5 @@ func convertResponseToProject(response client.ProjectResponse, plan Project) Pro TrustedIps: tip, ProtectionBypassForAutomation: protectionBypass, ProtectionBypassForAutomationSecret: protectionBypassSecret, - } + }, nil } diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go index 11a7a3da..70b82b68 100644 --- a/vercel/resource_project_test.go +++ b/vercel/resource_project_test.go @@ -354,6 +354,12 @@ resource "vercel_project" "test" { key = "bar" value = "baz" target = ["production"] + }, + { + key = "sensitive_thing" + value = "bar_updated" + target = ["production"] + sensitive = true } ] } @@ -594,6 +600,12 @@ resource "vercel_project" "test" { key = "oh_no" value = "bar" target = ["production"] + }, + { + key = "sensitive_thing" + value = "bar" + target = ["production"] + sensitive = true } ] } diff --git a/vercel/validator_serverless_function_region.go b/vercel/validator_serverless_function_region.go index a6767d6c..43d42a1b 100644 --- a/vercel/validator_serverless_function_region.go +++ b/vercel/validator_serverless_function_region.go @@ -33,15 +33,6 @@ func (v validatorServerlessFunctionRegion) MarkdownDescription(ctx context.Conte return fmt.Sprintf("The serverless function region provided is not supported on Vercel. Must be one of `%s`.", strings.Join(keys(v.regions), "`, `")) } -func contains(items []string, i string) bool { - for _, j := range items { - if j == i { - return true - } - } - return false -} - func keys(v map[string]struct{}) (out []string) { for k := range v { out = append(out, k) From 8b67dccccf8e642d2ed6b4fcd5ff90c680dd18be Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Tue, 6 Feb 2024 11:01:08 +0000 Subject: [PATCH 7/7] Bump test matrix versions --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7b66e9fe..a83a142c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,8 +75,8 @@ jobs: - "ubuntu-latest" - "windows-latest" terraform: - - "1.6.*" - - "1.0.*" + - "1.7.*" + - "1.4.*" runs-on: ${{ matrix.os }} steps: - name: Set up Go