From 8d5076a41dc0b36a352418b69b7f23e40088e2f1 Mon Sep 17 00:00:00 2001 From: Luke Phillips-Sheard Date: Mon, 29 Jul 2024 18:59:04 +0100 Subject: [PATCH 1/5] Implement vercel_edge_config_item --- client/edge_config_item.go | 120 +++++++++++++ vercel/resource_edge_config_item.go | 260 ++++++++++++++++++++++++++++ 2 files changed, 380 insertions(+) create mode 100644 client/edge_config_item.go create mode 100644 vercel/resource_edge_config_item.go diff --git a/client/edge_config_item.go b/client/edge_config_item.go new file mode 100644 index 00000000..529439fb --- /dev/null +++ b/client/edge_config_item.go @@ -0,0 +1,120 @@ +package client + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +type EdgeConfigOperation struct { + Operation string `json:"operation"` + Key string `json:"key"` + Value string `json:"value"` +} + +type EdgeConfigItem struct { + TeamID string + Key string `json:"key"` + Value string `json:"value"` + EdgeConfigID string `json:"edgeConfigId"` +} + +type CreateEdgeConfigItemRequest struct { + EdgeConfigID string + TeamID string + Token string + Key string + Value string +} + +func (c *Client) CreateEdgeConfigItem(ctx context.Context, request CreateEdgeConfigItemRequest) (e EdgeConfigItem, err error) { + url := fmt.Sprintf("%s/v1/edge-config/%s/items?token=%s", c.baseURL, request.EdgeConfigID, request.Token) + if c.teamID(request.TeamID) != "" { + url = fmt.Sprintf("%s&teamId=%s", url, c.teamID(request.TeamID)) + } + + payload := string(mustMarshal( + []EdgeConfigOperation{ + { + Operation: "upsert", + Key: request.Key, + Value: request.Value, + }, + }, + )) + tflog.Info(ctx, "creating edge config token", map[string]interface{}{ + "url": url, + "payload": payload, + }) + err = c.doRequest(clientRequest{ + ctx: ctx, + method: "PATCH", + url: url, + body: payload, + }, &e) + e.Key = request.Key + e.Value = request.Value + e.EdgeConfigID = request.EdgeConfigID + return e, err +} + +type EdgeConfigItemRequest struct { + EdgeConfigID string + TeamID string + Token string + Key string + Value string +} + +func (c *Client) DeleteEdgeConfigItem(ctx context.Context, request EdgeConfigItemRequest) error { + url := fmt.Sprintf("%s/v1/edge-config/%s/items", c.baseURL, request.EdgeConfigID) + if c.teamID(request.TeamID) != "" { + url = fmt.Sprintf("%s&teamId=%s", url, c.teamID(request.TeamID)) + } + + payload := string(mustMarshal( + []EdgeConfigOperation{ + { + Operation: "delete", + Key: request.Key, + Value: request.Value, + }, + }, + )) + + tflog.Info(ctx, "deleting edge config token", map[string]interface{}{ + "url": url, + "payload": payload, + }) + return c.doRequest(clientRequest{ + ctx: ctx, + method: "PATCH", + url: url, + body: payload, + }, nil) +} + +func (c *Client) GetEdgeConfigItem(ctx context.Context, request EdgeConfigItemRequest) (e EdgeConfigItem, err error) { + url := fmt.Sprintf("%s/v1/edge-config/%s/item/%s?token=%s", c.baseURL, request.EdgeConfigID, request.Key, request.Token) + if c.teamID(request.TeamID) != "" { + url = fmt.Sprintf("%s&teamId=%s", url, c.teamID(request.TeamID)) + } + + tflog.Info(ctx, "getting edge config token", map[string]interface{}{ + "url": url, + }) + + var Value string + err = c.doRequest(clientRequest{ + ctx: ctx, + method: "GET", + url: url, + }, &Value) + + e.EdgeConfigID = request.EdgeConfigID + e.Key = request.Key + e.Value = Value + + return e, err +} diff --git a/vercel/resource_edge_config_item.go b/vercel/resource_edge_config_item.go new file mode 100644 index 00000000..ef348bc5 --- /dev/null +++ b/vercel/resource_edge_config_item.go @@ -0,0 +1,260 @@ +package vercel + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/vercel/terraform-provider-vercel/client" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &edgeConfigItemResource{} + _ resource.ResourceWithConfigure = &edgeConfigItemResource{} +) + +type edgeConfigItemResource struct { + client *client.Client +} + +func (r *edgeConfigItemResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_edge_config_item" +} + +func (r *edgeConfigItemResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = client +} + +// Schema returns the schema information for an edgeConfigToken resource. +func (r *edgeConfigItemResource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: ` +Provides an Edge Config Item. + +An Edge Config is a global data store that enables experimentation with feature flags, A/B testing, critical redirects, and more. + +An Edge Config item is a value within an Edge Config. +`, + Attributes: map[string]schema.Attribute{ + "edge_config_id": schema.StringAttribute{ + Description: "The name of the key you want to add to or update within your Edge Config.", + Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "team_id": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The ID of the team the Edge Config should exist under. Required when configuring a team resource if a default team has not been set in the provider.", + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplaceIfConfigured(), stringplanmodifier.UseStateForUnknown()}, + }, + "key": schema.StringAttribute{ + Description: "The ID of the Edge Config store.", + Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "value": schema.StringAttribute{ + Description: "The value you want to assign to the key.", + Required: true, + }, + }, + } +} + +type EdgeConfigItem struct { + Key types.String `tfsdk:"key"` + Value types.String `tfsdk:"value"` + TeamID types.String `tfsdk:"team_id"` + EdgeConfigID types.String `tfsdk:"edge_config_id"` + Token types.String `tfsdk:"token"` +} + +func responseToEdgeConfigItem(out client.EdgeConfigItem) EdgeConfigItem { + return EdgeConfigItem{ + Key: types.StringValue(out.Key), + Value: types.StringValue(out.Value), + } +} + +// Create will create an edgeConfigToken within Vercel. +// This is called automatically by the provider when a new resource should be created. +func (r *edgeConfigItemResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan EdgeConfigItem + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + out, err := r.client.CreateEdgeConfigItem(ctx, client.CreateEdgeConfigItemRequest{ + TeamID: plan.TeamID.ValueString(), + EdgeConfigID: plan.EdgeConfigID.ValueString(), + Token: plan.Token.ValueString(), + Key: plan.Key.ValueString(), + Value: plan.Value.ValueString(), + }) + if err != nil { + resp.Diagnostics.AddError( + "Error creating Edge Config Item", + "Could not create Edge Config Item, unexpected error: "+err.Error(), + ) + return + } + + result := responseToEdgeConfigItem(out) + tflog.Info(ctx, "created Edge Config Item", map[string]interface{}{ + "edge_config_id": plan.EdgeConfigID.ValueString(), + "key": result.Key.ValueString(), + "value": result.Value.ValueString(), + }) + + diags = resp.State.Set(ctx, result) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read will read edgeConfigToken information by requesting it from the Vercel API, and will update terraform +// with this information. +func (r *edgeConfigItemResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state EdgeConfigItem + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + out, err := r.client.GetEdgeConfigItem(ctx, client.EdgeConfigItemRequest{ + Token: state.Token.ValueString(), + TeamID: state.TeamID.ValueString(), + EdgeConfigID: state.EdgeConfigID.ValueString(), + Key: state.Key.ValueString(), + Value: state.Value.ValueString(), + }) + if client.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + "Error reading Edge Config Item", + fmt.Sprintf("Could not get Edge Config Item %s %s, unexpected error: %s", + state.TeamID.ValueString(), + state.Key.ValueString(), + err, + ), + ) + return + } + + result := responseToEdgeConfigItem(out) + tflog.Info(ctx, "read edge config token", map[string]interface{}{ + "team_id": state.TeamID.ValueString(), + "edge_config_id": state.EdgeConfigID.ValueString(), + "key": state.Key.ValueString(), + }) + + diags = resp.State.Set(ctx, result) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Update is the same as Create +func (r *edgeConfigItemResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan EdgeConfigItem + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + out, err := r.client.CreateEdgeConfigItem(ctx, client.CreateEdgeConfigItemRequest{ + TeamID: plan.TeamID.ValueString(), + EdgeConfigID: plan.EdgeConfigID.ValueString(), + Token: plan.Token.ValueString(), + Key: plan.Key.ValueString(), + Value: plan.Value.ValueString(), + }) + if err != nil { + resp.Diagnostics.AddError( + "Error creating Edge Config Item", + "Could not create Edge Config Item, unexpected error: "+err.Error(), + ) + return + } + + result := responseToEdgeConfigItem(out) + tflog.Info(ctx, "created Edge Config Item", map[string]interface{}{ + "edge_config_id": plan.EdgeConfigID.ValueString(), + "key": result.Key.ValueString(), + "value": result.Value.ValueString(), + }) + + diags = resp.State.Set(ctx, result) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Delete deletes an Edge Config. +func (r *edgeConfigItemResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state EdgeConfigItem + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + err := r.client.DeleteEdgeConfigItem(ctx, client.EdgeConfigItemRequest{ + Token: state.Token.ValueString(), + TeamID: state.TeamID.ValueString(), + EdgeConfigID: state.EdgeConfigID.ValueString(), + Key: state.Key.ValueString(), + Value: state.Value.ValueString(), + }) + if client.NotFound(err) { + return + } + if err != nil { + resp.Diagnostics.AddError( + "Error deleting Edge Config Token", + fmt.Sprintf( + "Could not delete Edge Config Token %s %s %s, unexpected error: %s", + state.TeamID.ValueString(), + state.EdgeConfigID.ValueString(), + state.Key.ValueString(), + err, + ), + ) + return + } + + tflog.Info(ctx, "deleted edge config token", map[string]interface{}{ + "team_id": state.TeamID.ValueString(), + "edge_config_id": state.EdgeConfigID.ValueString(), + "key": state.Key.ValueString(), + }) +} From 7bca583e21c86c460ff166d4eb19f28413be9719 Mon Sep 17 00:00:00 2001 From: Luke Phillips-Sheard Date: Tue, 30 Jul 2024 11:33:21 +0100 Subject: [PATCH 2/5] Remove Token requirement on resource and make attributes require replace to avoid writing an update API. --- client/edge_config_item.go | 10 ++--- vercel/resource_edge_config_item.go | 63 +++++++---------------------- 2 files changed, 18 insertions(+), 55 deletions(-) diff --git a/client/edge_config_item.go b/client/edge_config_item.go index 529439fb..c2aba195 100644 --- a/client/edge_config_item.go +++ b/client/edge_config_item.go @@ -23,15 +23,14 @@ type EdgeConfigItem struct { type CreateEdgeConfigItemRequest struct { EdgeConfigID string TeamID string - Token string Key string Value string } func (c *Client) CreateEdgeConfigItem(ctx context.Context, request CreateEdgeConfigItemRequest) (e EdgeConfigItem, err error) { - url := fmt.Sprintf("%s/v1/edge-config/%s/items?token=%s", c.baseURL, request.EdgeConfigID, request.Token) + url := fmt.Sprintf("%s/v1/edge-config/%s/items", c.baseURL, request.EdgeConfigID) if c.teamID(request.TeamID) != "" { - url = fmt.Sprintf("%s&teamId=%s", url, c.teamID(request.TeamID)) + url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID)) } payload := string(mustMarshal( @@ -62,7 +61,6 @@ func (c *Client) CreateEdgeConfigItem(ctx context.Context, request CreateEdgeCon type EdgeConfigItemRequest struct { EdgeConfigID string TeamID string - Token string Key string Value string } @@ -96,9 +94,9 @@ func (c *Client) DeleteEdgeConfigItem(ctx context.Context, request EdgeConfigIte } func (c *Client) GetEdgeConfigItem(ctx context.Context, request EdgeConfigItemRequest) (e EdgeConfigItem, err error) { - url := fmt.Sprintf("%s/v1/edge-config/%s/item/%s?token=%s", c.baseURL, request.EdgeConfigID, request.Key, request.Token) + url := fmt.Sprintf("%s/v1/edge-config/%s/item/%s", c.baseURL, request.EdgeConfigID, request.Key) if c.teamID(request.TeamID) != "" { - url = fmt.Sprintf("%s&teamId=%s", url, c.teamID(request.TeamID)) + url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID)) } tflog.Info(ctx, "getting edge config token", map[string]interface{}{ diff --git a/vercel/resource_edge_config_item.go b/vercel/resource_edge_config_item.go index ef348bc5..4264d095 100644 --- a/vercel/resource_edge_config_item.go +++ b/vercel/resource_edge_config_item.go @@ -2,6 +2,7 @@ package vercel import ( "context" + "errors" "fmt" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -73,25 +74,27 @@ An Edge Config item is a value within an Edge Config. PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, }, "value": schema.StringAttribute{ - Description: "The value you want to assign to the key.", - Required: true, + Description: "The value you want to assign to the key.", + Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, }, }, } } type EdgeConfigItem struct { + EdgeConfigID types.String `tfsdk:"edge_config_id"` + TeamID types.String `tfsdk:"team_id"` Key types.String `tfsdk:"key"` Value types.String `tfsdk:"value"` - TeamID types.String `tfsdk:"team_id"` - EdgeConfigID types.String `tfsdk:"edge_config_id"` - Token types.String `tfsdk:"token"` } func responseToEdgeConfigItem(out client.EdgeConfigItem) EdgeConfigItem { return EdgeConfigItem{ - Key: types.StringValue(out.Key), - Value: types.StringValue(out.Value), + EdgeConfigID: types.StringValue(out.EdgeConfigID), + TeamID: types.StringValue(out.TeamID), + Key: types.StringValue(out.Key), + Value: types.StringValue(out.Value), } } @@ -108,7 +111,6 @@ func (r *edgeConfigItemResource) Create(ctx context.Context, req resource.Create out, err := r.client.CreateEdgeConfigItem(ctx, client.CreateEdgeConfigItemRequest{ TeamID: plan.TeamID.ValueString(), EdgeConfigID: plan.EdgeConfigID.ValueString(), - Token: plan.Token.ValueString(), Key: plan.Key.ValueString(), Value: plan.Value.ValueString(), }) @@ -145,11 +147,9 @@ func (r *edgeConfigItemResource) Read(ctx context.Context, req resource.ReadRequ } out, err := r.client.GetEdgeConfigItem(ctx, client.EdgeConfigItemRequest{ - Token: state.Token.ValueString(), - TeamID: state.TeamID.ValueString(), EdgeConfigID: state.EdgeConfigID.ValueString(), + TeamID: state.TeamID.ValueString(), Key: state.Key.ValueString(), - Value: state.Value.ValueString(), }) if client.NotFound(err) { resp.State.RemoveResource(ctx) @@ -169,8 +169,8 @@ func (r *edgeConfigItemResource) Read(ctx context.Context, req resource.ReadRequ result := responseToEdgeConfigItem(out) tflog.Info(ctx, "read edge config token", map[string]interface{}{ - "team_id": state.TeamID.ValueString(), "edge_config_id": state.EdgeConfigID.ValueString(), + "team_id": state.TeamID.ValueString(), "key": state.Key.ValueString(), }) @@ -183,40 +183,7 @@ func (r *edgeConfigItemResource) Read(ctx context.Context, req resource.ReadRequ // Update is the same as Create func (r *edgeConfigItemResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var plan EdgeConfigItem - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - out, err := r.client.CreateEdgeConfigItem(ctx, client.CreateEdgeConfigItemRequest{ - TeamID: plan.TeamID.ValueString(), - EdgeConfigID: plan.EdgeConfigID.ValueString(), - Token: plan.Token.ValueString(), - Key: plan.Key.ValueString(), - Value: plan.Value.ValueString(), - }) - if err != nil { - resp.Diagnostics.AddError( - "Error creating Edge Config Item", - "Could not create Edge Config Item, unexpected error: "+err.Error(), - ) - return - } - - result := responseToEdgeConfigItem(out) - tflog.Info(ctx, "created Edge Config Item", map[string]interface{}{ - "edge_config_id": plan.EdgeConfigID.ValueString(), - "key": result.Key.ValueString(), - "value": result.Value.ValueString(), - }) - - diags = resp.State.Set(ctx, result) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } + panic(errors.New("Update is not supported, attributes require replacement")) } // Delete deletes an Edge Config. @@ -229,11 +196,9 @@ func (r *edgeConfigItemResource) Delete(ctx context.Context, req resource.Delete } err := r.client.DeleteEdgeConfigItem(ctx, client.EdgeConfigItemRequest{ - Token: state.Token.ValueString(), TeamID: state.TeamID.ValueString(), EdgeConfigID: state.EdgeConfigID.ValueString(), Key: state.Key.ValueString(), - Value: state.Value.ValueString(), }) if client.NotFound(err) { return @@ -253,8 +218,8 @@ func (r *edgeConfigItemResource) Delete(ctx context.Context, req resource.Delete } tflog.Info(ctx, "deleted edge config token", map[string]interface{}{ - "team_id": state.TeamID.ValueString(), "edge_config_id": state.EdgeConfigID.ValueString(), + "team_id": state.TeamID.ValueString(), "key": state.Key.ValueString(), }) } From 6cf531f056838a10b8cd10d0919c85acba1a96f6 Mon Sep 17 00:00:00 2001 From: Luke Phillips-Sheard Date: Wed, 31 Jul 2024 07:46:51 +0100 Subject: [PATCH 3/5] Create Edge Config Item datasource --- vercel/data_source_edge_config_item.go | 116 +++++++++++++++++++++++++ vercel/provider.go | 2 + vercel/resource_edge_config_item.go | 51 ++++++++++- 3 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 vercel/data_source_edge_config_item.go diff --git a/vercel/data_source_edge_config_item.go b/vercel/data_source_edge_config_item.go new file mode 100644 index 00000000..242dcaed --- /dev/null +++ b/vercel/data_source_edge_config_item.go @@ -0,0 +1,116 @@ +package vercel + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/vercel/terraform-provider-vercel/client" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ datasource.DataSource = &edgeConfigItemDataSource{} + _ datasource.DataSourceWithConfigure = &edgeConfigItemDataSource{} +) + +func newEdgeConfigItemDataSource() datasource.DataSource { + return &edgeConfigItemDataSource{} +} + +type edgeConfigItemDataSource struct { + client *client.Client +} + +func (d *edgeConfigItemDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_edge_config_item" +} + +func (d *edgeConfigItemDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.Client) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + d.client = client +} + +// Schema returns the schema information for an edgeConfigItem data source +func (r *edgeConfigItemDataSource) Schema(_ context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: ` +Provides the value of an existing Edge Config Item. + +An Edge Config is a global data store that enables experimentation with feature flags, A/B testing, critical redirects, and more. + +An Edge Config Item is a value within an Edge Config.`, + Attributes: map[string]schema.Attribute{ + "edge_config_id": schema.StringAttribute{ + Description: "The name of the key you want to add to or update within your Edge Config.", + Required: true, + }, + "team_id": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "The ID of the team the Edge Config should exist under. Required when configuring a team resource if a default team has not been set in the provider.", + }, + "key": schema.StringAttribute{ + Description: "The ID of the Edge Config store.", + Required: true, + }, + }, + } +} + +// Read will read the edgeConfigItem information by requesting it from the Vercel API, and will update terraform +// with this information. +// It is called by the provider whenever data source values should be read to update state. +func (d *edgeConfigItemDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config EdgeConfigItem + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + out, err := d.client.GetEdgeConfigItem(ctx, client.EdgeConfigItemRequest{ + EdgeConfigID: config.EdgeConfigID.ValueString(), + TeamID: config.TeamID.ValueString(), + Key: config.Key.ValueString(), + }) + if err != nil { + resp.Diagnostics.AddError( + "Error reading EdgeConfigItem", + fmt.Sprintf("Could not get Edge Config Item %s %s, unexpected error: %s", + config.EdgeConfigID.ValueString(), + config.Key.ValueString(), + err, + ), + ) + return + } + + result := responseToEdgeConfigItem(out) + tflog.Info(ctx, "read edge config item", map[string]interface{}{ + "edge_config_id": result.EdgeConfigID.ValueString(), + "team_id": result.TeamID.ValueString(), + "key": result.Key.ValueString(), + }) + + diags = resp.State.Set(ctx, result) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} diff --git a/vercel/provider.go b/vercel/provider.go index 5162fb8d..f741f3f4 100644 --- a/vercel/provider.go +++ b/vercel/provider.go @@ -55,6 +55,7 @@ func (p *vercelProvider) Resources(_ context.Context) []func() resource.Resource newDNSRecordResource, newDeploymentResource, newEdgeConfigResource, + newEdgeConfigItemResource, newEdgeConfigSchemaResource, newEdgeConfigTokenResource, newFirewallConfigResource, @@ -75,6 +76,7 @@ func (p *vercelProvider) DataSources(_ context.Context) []func() datasource.Data newAttackChallengeModeDataSource, newDeploymentDataSource, newEdgeConfigDataSource, + newEdgeConfigItemDataSource, newEdgeConfigSchemaDataSource, newEdgeConfigTokenDataSource, newEndpointVerificationDataSource, diff --git a/vercel/resource_edge_config_item.go b/vercel/resource_edge_config_item.go index 4264d095..7310426e 100644 --- a/vercel/resource_edge_config_item.go +++ b/vercel/resource_edge_config_item.go @@ -20,6 +20,10 @@ var ( _ resource.ResourceWithConfigure = &edgeConfigItemResource{} ) +func newEdgeConfigItemResource() resource.Resource { + return &edgeConfigItemResource{} +} + type edgeConfigItemResource struct { client *client.Client } @@ -54,7 +58,7 @@ Provides an Edge Config Item. An Edge Config is a global data store that enables experimentation with feature flags, A/B testing, critical redirects, and more. -An Edge Config item is a value within an Edge Config. +An Edge Config Item is a value within an Edge Config. `, Attributes: map[string]schema.Attribute{ "edge_config_id": schema.StringAttribute{ @@ -223,3 +227,48 @@ func (r *edgeConfigItemResource) Delete(ctx context.Context, req resource.Delete "key": state.Key.ValueString(), }) } + +func (r *edgeConfigItemResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + teamID, edgeConfigId, id, ok := splitInto2Or3(req.ID) + if !ok { + resp.Diagnostics.AddError( + "Error importing Edge Config Item", + fmt.Sprintf("Invalid id '%s' specified. should be in format \"team_id/edge_config_id/key\" or \"edge_config_id/key\"", req.ID), + ) + } + + out, err := r.client.GetEdgeConfigItem(ctx, client.EdgeConfigItemRequest{ + EdgeConfigID: edgeConfigId, + TeamID: teamID, + Key: id, + }) + if client.NotFound(err) { + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError( + "Error reading Edge Config Item", + fmt.Sprintf("Could not get Edge Config Item %s %s %s, unexpected error: %s", + teamID, + edgeConfigId, + id, + err, + ), + ) + return + } + + result := responseToEdgeConfigItem(out) + tflog.Info(ctx, "import edge config schema", map[string]interface{}{ + "team_id": result.TeamID.ValueString(), + "edge_config_id": result.EdgeConfigID.ValueString(), + "key": result.Key.ValueString(), + }) + + diags := resp.State.Set(ctx, result) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} From 3d34c7fce9fecfad3f168ee9a0d5eb6279db2263 Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Thu, 10 Oct 2024 17:08:54 +0100 Subject: [PATCH 4/5] Fix implementation --- client/edge_config_item.go | 69 +++++++----- vercel/data_source_edge_config_item.go | 24 +++-- vercel/data_source_edge_config_item_test.go | 70 +++++++++++++ vercel/resource_edge_config_item.go | 13 +-- vercel/resource_edge_config_item_test.go | 110 ++++++++++++++++++++ vercel/resource_edge_config_test.go | 2 +- 6 files changed, 247 insertions(+), 41 deletions(-) create mode 100644 vercel/data_source_edge_config_item_test.go create mode 100644 vercel/resource_edge_config_item_test.go diff --git a/client/edge_config_item.go b/client/edge_config_item.go index c2aba195..f4c43058 100644 --- a/client/edge_config_item.go +++ b/client/edge_config_item.go @@ -29,16 +29,21 @@ type CreateEdgeConfigItemRequest struct { func (c *Client) CreateEdgeConfigItem(ctx context.Context, request CreateEdgeConfigItemRequest) (e EdgeConfigItem, err error) { url := fmt.Sprintf("%s/v1/edge-config/%s/items", c.baseURL, request.EdgeConfigID) - if c.teamID(request.TeamID) != "" { - url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID)) + teamID := c.teamID(request.TeamID) + if teamID != "" { + url = fmt.Sprintf("%s?teamId=%s", url, teamID) } payload := string(mustMarshal( - []EdgeConfigOperation{ - { - Operation: "upsert", - Key: request.Key, - Value: request.Value, + struct { + Items []EdgeConfigOperation `json:"items"` + }{ + Items: []EdgeConfigOperation{ + { + Operation: "upsert", + Key: request.Key, + Value: request.Value, + }, }, }, )) @@ -51,11 +56,14 @@ func (c *Client) CreateEdgeConfigItem(ctx context.Context, request CreateEdgeCon method: "PATCH", url: url, body: payload, - }, &e) - e.Key = request.Key - e.Value = request.Value - e.EdgeConfigID = request.EdgeConfigID - return e, err + }, nil) + + return EdgeConfigItem{ + Key: request.Key, + Value: request.Value, + EdgeConfigID: request.EdgeConfigID, + TeamID: teamID, + }, err } type EdgeConfigItemRequest struct { @@ -68,15 +76,19 @@ type EdgeConfigItemRequest struct { func (c *Client) DeleteEdgeConfigItem(ctx context.Context, request EdgeConfigItemRequest) error { url := fmt.Sprintf("%s/v1/edge-config/%s/items", c.baseURL, request.EdgeConfigID) if c.teamID(request.TeamID) != "" { - url = fmt.Sprintf("%s&teamId=%s", url, c.teamID(request.TeamID)) + url = fmt.Sprintf("%s?teamId=%s", url, c.teamID(request.TeamID)) } payload := string(mustMarshal( - []EdgeConfigOperation{ - { - Operation: "delete", - Key: request.Key, - Value: request.Value, + struct { + Items []EdgeConfigOperation `json:"items"` + }{ + Items: []EdgeConfigOperation{ + { + Operation: "delete", + Key: request.Key, + Value: request.Value, + }, }, }, )) @@ -103,16 +115,21 @@ func (c *Client) GetEdgeConfigItem(ctx context.Context, request EdgeConfigItemRe "url": url, }) - var Value string err = c.doRequest(clientRequest{ - ctx: ctx, - method: "GET", - url: url, - }, &Value) + ctx: ctx, + method: "GET", + url: url, + errorOnNoContent: true, + }, &e) - e.EdgeConfigID = request.EdgeConfigID - e.Key = request.Key - e.Value = Value + if noContent(err) { + return e, APIError{ + StatusCode: 404, + Message: "Edge Config Item not found", + Code: "not_found", + } + } + e.TeamID = c.teamID(request.TeamID) return e, err } diff --git a/vercel/data_source_edge_config_item.go b/vercel/data_source_edge_config_item.go index 242dcaed..9ea09747 100644 --- a/vercel/data_source_edge_config_item.go +++ b/vercel/data_source_edge_config_item.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/vercel/terraform-provider-vercel/client" ) @@ -56,28 +57,39 @@ An Edge Config is a global data store that enables experimentation with feature An Edge Config Item is a value within an Edge Config.`, Attributes: map[string]schema.Attribute{ - "edge_config_id": schema.StringAttribute{ - Description: "The name of the key you want to add to or update within your Edge Config.", + "id": schema.StringAttribute{ + Description: "The ID of the Edge Config that the item should exist under.", Required: true, }, "team_id": schema.StringAttribute{ + Description: "The ID of the team the Edge Config should exist under. Required when configuring a team resource if a default team has not been set in the provider.", Optional: true, Computed: true, - Description: "The ID of the team the Edge Config should exist under. Required when configuring a team resource if a default team has not been set in the provider.", }, "key": schema.StringAttribute{ - Description: "The ID of the Edge Config store.", + Description: "The name of the key you want to retrieve within your Edge Config.", Required: true, }, + "value": schema.StringAttribute{ + Description: "The value assigned to the key.", + Computed: true, + }, }, } } +type EdgeConfigItemDataSource struct { + EdgeConfigID types.String `tfsdk:"id"` + TeamID types.String `tfsdk:"team_id"` + Key types.String `tfsdk:"key"` + Value types.String `tfsdk:"value"` +} + // Read will read the edgeConfigItem information by requesting it from the Vercel API, and will update terraform // with this information. // It is called by the provider whenever data source values should be read to update state. func (d *edgeConfigItemDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var config EdgeConfigItem + var config EdgeConfigItemDataSource diags := req.Config.Get(ctx, &config) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -108,7 +120,7 @@ func (d *edgeConfigItemDataSource) Read(ctx context.Context, req datasource.Read "key": result.Key.ValueString(), }) - diags = resp.State.Set(ctx, result) + diags = resp.State.Set(ctx, EdgeConfigItemDataSource(result)) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return diff --git a/vercel/data_source_edge_config_item_test.go b/vercel/data_source_edge_config_item_test.go new file mode 100644 index 00000000..fb28eb2a --- /dev/null +++ b/vercel/data_source_edge_config_item_test.go @@ -0,0 +1,70 @@ +package vercel_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAcc_EdgeConfigItemDataSource(t *testing.T) { + name := acctest.RandString(16) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccEdgeConfigItemDataSourceConfig(name, teamIDConfig()), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrSet("data.vercel_edge_config_item.test", "id"), + resource.TestCheckResourceAttrSet("data.vercel_edge_config_item.test", "team_id"), + resource.TestCheckResourceAttr("data.vercel_edge_config_item.test", "key", "foobar"), + resource.TestCheckResourceAttr("data.vercel_edge_config_item.test", "value", "baz"), + ), + }, + { + Config: testAccEdgeConfigItemDataSourceConfigNoItem(name, teamIDConfig()), + ExpectError: regexp.MustCompile("not_found"), + }, + }, + }) +} + +func testAccEdgeConfigItemDataSourceConfig(name, teamID string) string { + return fmt.Sprintf(` +resource "vercel_edge_config" "test" { + name = "%[1]s" + %[2]s +} + +resource "vercel_edge_config_item" "test" { + edge_config_id = vercel_edge_config.test.id + key = "foobar" + value = "baz" + %[2]s +} + +data "vercel_edge_config_item" "test" { + id = vercel_edge_config_item.test.edge_config_id + key = "foobar" + %[2]s +} +`, name, teamID) +} + +func testAccEdgeConfigItemDataSourceConfigNoItem(name, teamID string) string { + return fmt.Sprintf(` +resource "vercel_edge_config" "test" { + name = "%[1]s" + %[2]s +} + +data "vercel_edge_config_item" "test" { + id = vercel_edge_config.test.id + key = "foobar" + %[2]s +} +`, name, teamID) +} diff --git a/vercel/resource_edge_config_item.go b/vercel/resource_edge_config_item.go index 7310426e..f7050ea9 100644 --- a/vercel/resource_edge_config_item.go +++ b/vercel/resource_edge_config_item.go @@ -62,7 +62,7 @@ An Edge Config Item is a value within an Edge Config. `, Attributes: map[string]schema.Attribute{ "edge_config_id": schema.StringAttribute{ - Description: "The name of the key you want to add to or update within your Edge Config.", + Description: "The ID of the Edge Config store.", Required: true, PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, }, @@ -73,7 +73,7 @@ An Edge Config Item is a value within an Edge Config. PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplaceIfConfigured(), stringplanmodifier.UseStateForUnknown()}, }, "key": schema.StringAttribute{ - Description: "The ID of the Edge Config store.", + Description: "The name of the key you want to add to or update within your Edge Config.", Required: true, PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, }, @@ -190,7 +190,7 @@ func (r *edgeConfigItemResource) Update(ctx context.Context, req resource.Update panic(errors.New("Update is not supported, attributes require replacement")) } -// Delete deletes an Edge Config. +// Delete deletes an Edge Config Item. func (r *edgeConfigItemResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var state EdgeConfigItem diags := req.State.Get(ctx, &state) @@ -204,14 +204,11 @@ func (r *edgeConfigItemResource) Delete(ctx context.Context, req resource.Delete EdgeConfigID: state.EdgeConfigID.ValueString(), Key: state.Key.ValueString(), }) - if client.NotFound(err) { - return - } if err != nil { resp.Diagnostics.AddError( - "Error deleting Edge Config Token", + "Error deleting Edge Config Item", fmt.Sprintf( - "Could not delete Edge Config Token %s %s %s, unexpected error: %s", + "Could not delete Edge Config Item %s %s %s, unexpected error: %s", state.TeamID.ValueString(), state.EdgeConfigID.ValueString(), state.Key.ValueString(), diff --git a/vercel/resource_edge_config_item_test.go b/vercel/resource_edge_config_item_test.go new file mode 100644 index 00000000..c52f7f3e --- /dev/null +++ b/vercel/resource_edge_config_item_test.go @@ -0,0 +1,110 @@ +package vercel_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/vercel/terraform-provider-vercel/client" +) + +func getEdgeConfigItemImportID(n string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, 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") + } + + return fmt.Sprintf("%s/%s/%s", rs.Primary.Attributes["team_id"], rs.Primary.Attributes["edge_config_id"], rs.Primary.Attributes["key"]), nil + } +} + +func testCheckEdgeConfigItemDeleted(n, key, 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().GetEdgeConfigItem(context.TODO(), client.EdgeConfigItemRequest{ + TeamID: teamID, + EdgeConfigID: rs.Primary.ID, + Key: key, + }) + if err == nil { + return fmt.Errorf("expected not_found error, but got no error") + } + if !client.NotFound(err) { + return fmt.Errorf("Unexpected error checking for deleted edge config item: %s", err) + } + + return nil + } +} + +func TestAcc_EdgeConfigItemResource(t *testing.T) { + name := acctest.RandString(16) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + CheckDestroy: testCheckEdgeConfigDeleted("vercel_edge_config.test_item", testTeam()), + Steps: []resource.TestStep{ + { + Config: testAccResourceEdgeConfigItem(name, teamIDConfig()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckEdgeConfigExists(testTeam(), "vercel_edge_config.test_item"), + resource.TestCheckResourceAttr("vercel_edge_config_item.test", "key", "foobar"), + resource.TestCheckResourceAttr("vercel_edge_config_item.test", "value", "baz"), + ), + }, + { + ResourceName: "vercel_edge_config_item.test", + ImportState: true, + ImportStateIdFunc: getEdgeConfigItemImportID("vercel_edge_config_item.test"), + }, + { + Config: testAccResourceEdgeConfigItemDeleted(name, teamIDConfig()), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckEdgeConfigExists(testTeam(), "vercel_edge_config.test_item"), + testCheckEdgeConfigItemDeleted("vercel_edge_config.test_item", "foobar", testTeam()), + ), + }, + }, + }) +} + +func testAccResourceEdgeConfigItem(name, team string) string { + return fmt.Sprintf(` +resource "vercel_edge_config" "test_item" { + name = "%[1]s" + %[2]s +} + +resource "vercel_edge_config_item" "test" { + edge_config_id = vercel_edge_config.test_item.id + key = "foobar" + value = "baz" + %[2]s +} +`, name, team) +} + +func testAccResourceEdgeConfigItemDeleted(name, team string) string { + return fmt.Sprintf(` +resource "vercel_edge_config" "test_item" { + name = "%[1]s" + %[2]s +} +`, name, team) +} diff --git a/vercel/resource_edge_config_test.go b/vercel/resource_edge_config_test.go index 7bd0dbba..6aa6f512 100644 --- a/vercel/resource_edge_config_test.go +++ b/vercel/resource_edge_config_test.go @@ -58,7 +58,7 @@ func TestAcc_EdgeConfigResource(t *testing.T) { CheckDestroy: testCheckEdgeConfigDeleted("vercel_edge_config.test", testTeam()), Steps: []resource.TestStep{ { - Config: testAccResourceEdgeConfig(name, teamIDConfig()), + Config: testAccResourceEdgeConfigItem(name, teamIDConfig()), Check: resource.ComposeAggregateTestCheckFunc( testCheckEdgeConfigExists(testTeam(), "vercel_edge_config.test"), resource.TestCheckResourceAttr("vercel_edge_config.test", "name", name), From 4dd189ad5fb187af9c403abea47f14f34968bc91 Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Fri, 11 Oct 2024 09:34:49 +0100 Subject: [PATCH 5/5] Flesh out docs + couple of additional fixes --- docs/data-sources/edge_config_item.md | 46 ++++++++++++++ docs/resources/edge_config_item.md | 62 +++++++++++++++++++ .../vercel_edge_config_item/data-source.tf | 8 +++ .../vercel_edge_config_item/import.sh | 11 ++++ .../vercel_edge_config_item/resource.tf | 9 +++ vercel/resource_edge_config_item.go | 6 +- vercel/resource_edge_config_test.go | 2 +- 7 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 docs/data-sources/edge_config_item.md create mode 100644 docs/resources/edge_config_item.md create mode 100644 examples/data-sources/vercel_edge_config_item/data-source.tf create mode 100644 examples/resources/vercel_edge_config_item/import.sh create mode 100644 examples/resources/vercel_edge_config_item/resource.tf diff --git a/docs/data-sources/edge_config_item.md b/docs/data-sources/edge_config_item.md new file mode 100644 index 00000000..2d853bf7 --- /dev/null +++ b/docs/data-sources/edge_config_item.md @@ -0,0 +1,46 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "vercel_edge_config_item Data Source - terraform-provider-vercel" +subcategory: "" +description: |- + Provides the value of an existing Edge Config Item. + An Edge Config is a global data store that enables experimentation with feature flags, A/B testing, critical redirects, and more. + An Edge Config Item is a value within an Edge Config. +--- + +# vercel_edge_config_item (Data Source) + +Provides the value of an existing Edge Config Item. + +An Edge Config is a global data store that enables experimentation with feature flags, A/B testing, critical redirects, and more. + +An Edge Config Item is a value within an Edge Config. + +## Example Usage + +```terraform +data "vercel_edge_config" "example" { + id = "ecfg_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" +} + +data "vercel_edge_config_item" "test" { + id = data.vercel_edge_config.example.id + key = "foobar" +} +``` + + +## Schema + +### Required + +- `id` (String) The ID of the Edge Config that the item should exist under. +- `key` (String) The name of the key you want to retrieve within your Edge Config. + +### Optional + +- `team_id` (String) The ID of the team the Edge Config should exist under. Required when configuring a team resource if a default team has not been set in the provider. + +### Read-Only + +- `value` (String) The value assigned to the key. diff --git a/docs/resources/edge_config_item.md b/docs/resources/edge_config_item.md new file mode 100644 index 00000000..77591acf --- /dev/null +++ b/docs/resources/edge_config_item.md @@ -0,0 +1,62 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "vercel_edge_config_item Resource - terraform-provider-vercel" +subcategory: "" +description: |- + Provides an Edge Config Item. + An Edge Config is a global data store that enables experimentation with feature flags, A/B testing, critical redirects, and more. + An Edge Config Item is a value within an Edge Config. +--- + +# vercel_edge_config_item (Resource) + +Provides an Edge Config Item. + +An Edge Config is a global data store that enables experimentation with feature flags, A/B testing, critical redirects, and more. + +An Edge Config Item is a value within an Edge Config. + +## Example Usage + +```terraform +resource "vercel_edge_config" "example" { + name = "example" +} + +resource "vercel_edge_config_item" "example" { + edge_config_id = vercel_edge_config.example.id + key = "foobar" + value = "baz" +} +``` + + +## Schema + +### Required + +- `edge_config_id` (String) The ID of the Edge Config store. +- `key` (String) The name of the key you want to add to or update within your Edge Config. +- `value` (String) The value you want to assign to the key. + +### Optional + +- `team_id` (String) The ID of the team the Edge Config should exist under. Required when configuring a team resource if a default team has not been set in the provider. + +## Import + +Import is supported using the following syntax: + +```shell +# If importing into a personal account, or with a team configured on +# the provider, simply use the edge config id and the key of the item to import. +# - edge_config_id can be found by navigating to the Edge Config in the Vercel UI. It should begin with `ecfg_`. +# - key is the key of teh item to import. +terraform import vercel_edge_config.example ecfg_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/example_key + +# Alternatively, you can import via the team_id, edge_config_id and the key of the item to import. +# - team_id can be found in the team `settings` tab in the Vercel UI. +# - edge_config_id can be found by navigating to the Edge Config in the Vercel UI. It should begin with `ecfg_`. +# - key is the key of the item to import. +terraform import vercel_edge_config.example team_xxxxxxxxxxxxxxxxxxxxxxxx/ecfg_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/example_key +``` diff --git a/examples/data-sources/vercel_edge_config_item/data-source.tf b/examples/data-sources/vercel_edge_config_item/data-source.tf new file mode 100644 index 00000000..dafea0eb --- /dev/null +++ b/examples/data-sources/vercel_edge_config_item/data-source.tf @@ -0,0 +1,8 @@ +data "vercel_edge_config" "example" { + id = "ecfg_xxxxxxxxxxxxxxxxxxxxxxxxxxxx" +} + +data "vercel_edge_config_item" "test" { + id = data.vercel_edge_config.example.id + key = "foobar" +} diff --git a/examples/resources/vercel_edge_config_item/import.sh b/examples/resources/vercel_edge_config_item/import.sh new file mode 100644 index 00000000..5fda4f9a --- /dev/null +++ b/examples/resources/vercel_edge_config_item/import.sh @@ -0,0 +1,11 @@ +# If importing into a personal account, or with a team configured on +# the provider, simply use the edge config id and the key of the item to import. +# - edge_config_id can be found by navigating to the Edge Config in the Vercel UI. It should begin with `ecfg_`. +# - key is the key of teh item to import. +terraform import vercel_edge_config.example ecfg_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/example_key + +# Alternatively, you can import via the team_id, edge_config_id and the key of the item to import. +# - team_id can be found in the team `settings` tab in the Vercel UI. +# - edge_config_id can be found by navigating to the Edge Config in the Vercel UI. It should begin with `ecfg_`. +# - key is the key of the item to import. +terraform import vercel_edge_config.example team_xxxxxxxxxxxxxxxxxxxxxxxx/ecfg_xxxxxxxxxxxxxxxxxxxxxxxxxxxx/example_key diff --git a/examples/resources/vercel_edge_config_item/resource.tf b/examples/resources/vercel_edge_config_item/resource.tf new file mode 100644 index 00000000..2ea599f1 --- /dev/null +++ b/examples/resources/vercel_edge_config_item/resource.tf @@ -0,0 +1,9 @@ +resource "vercel_edge_config" "example" { + name = "example" +} + +resource "vercel_edge_config_item" "example" { + edge_config_id = vercel_edge_config.example.id + key = "foobar" + value = "baz" +} diff --git a/vercel/resource_edge_config_item.go b/vercel/resource_edge_config_item.go index f7050ea9..c37b5dc1 100644 --- a/vercel/resource_edge_config_item.go +++ b/vercel/resource_edge_config_item.go @@ -2,7 +2,6 @@ package vercel import ( "context" - "errors" "fmt" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -187,7 +186,10 @@ func (r *edgeConfigItemResource) Read(ctx context.Context, req resource.ReadRequ // Update is the same as Create func (r *edgeConfigItemResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - panic(errors.New("Update is not supported, attributes require replacement")) + resp.Diagnostics.AddError( + "Updating an Edge Config Item is not supported. If you see this error, this is a bug in the provider.", + "Updating an Edge Config Item is not supported. If you see this error, this is a bug in the provider.", + ) } // Delete deletes an Edge Config Item. diff --git a/vercel/resource_edge_config_test.go b/vercel/resource_edge_config_test.go index 6aa6f512..7bd0dbba 100644 --- a/vercel/resource_edge_config_test.go +++ b/vercel/resource_edge_config_test.go @@ -58,7 +58,7 @@ func TestAcc_EdgeConfigResource(t *testing.T) { CheckDestroy: testCheckEdgeConfigDeleted("vercel_edge_config.test", testTeam()), Steps: []resource.TestStep{ { - Config: testAccResourceEdgeConfigItem(name, teamIDConfig()), + Config: testAccResourceEdgeConfig(name, teamIDConfig()), Check: resource.ComposeAggregateTestCheckFunc( testCheckEdgeConfigExists(testTeam(), "vercel_edge_config.test"), resource.TestCheckResourceAttr("vercel_edge_config.test", "name", name),