Skip to content

Commit a6b09bd

Browse files
authored
Add resource for adding single projects to shared environment variables (#275)
1 parent 558f3e5 commit a6b09bd

6 files changed

+500
-7
lines changed

client/shared_environment_variable.go

+13-7
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,20 @@ func (c *Client) ListSharedEnvironmentVariables(ctx context.Context, teamID stri
163163
return res.Data, err
164164
}
165165

166+
type UpdateSharedEnvironmentVariableRequestProjectIDUpdates struct {
167+
Link []string `json:"link,omitempty"`
168+
Unlink []string `json:"unlink,omitempty"`
169+
}
170+
166171
type UpdateSharedEnvironmentVariableRequest struct {
167-
Value string `json:"value"`
168-
Type string `json:"type"`
169-
ProjectIDs []string `json:"projectId"`
170-
Target []string `json:"target"`
171-
Comment string `json:"comment"`
172-
TeamID string `json:"-"`
173-
EnvID string `json:"-"`
172+
Value string `json:"value,omitempty"`
173+
Type string `json:"type,omitempty"`
174+
ProjectIDs []string `json:"projectId,omitempty"`
175+
ProjectIDUpdates UpdateSharedEnvironmentVariableRequestProjectIDUpdates `json:"projectIdUpdates,omitempty"`
176+
Target []string `json:"target,omitempty"`
177+
Comment string `json:"comment,omitempty"`
178+
TeamID string `json:"-"`
179+
EnvID string `json:"-"`
174180
}
175181

176182
func (c *Client) UpdateSharedEnvironmentVariable(ctx context.Context, request UpdateSharedEnvironmentVariableRequest) (e SharedEnvironmentVariableResponse, err error) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "vercel_shared_environment_variable_project_link Resource - terraform-provider-vercel"
4+
subcategory: ""
5+
description: |-
6+
Links a project to a Shared Environment Variable.
7+
~> This resource is intended to be used alongside a vercel_shared_environment_variable Data Source, not the Resource. The Resource also defines which projects to link to the shared environment variable, and using both vercel_shared_environment_variable and vercel_shared_environment_variable_project_link together results in undefined behavior.
8+
---
9+
10+
# vercel_shared_environment_variable_project_link (Resource)
11+
12+
Links a project to a Shared Environment Variable.
13+
14+
~> This resource is intended to be used alongside a vercel_shared_environment_variable Data Source, not the Resource. The Resource also defines which projects to link to the shared environment variable, and using both vercel_shared_environment_variable and vercel_shared_environment_variable_project_link together results in undefined behavior.
15+
16+
## Example Usage
17+
18+
```terraform
19+
data "vercel_shared_environment_variable" "example" {
20+
key = "EXAMPLE_ENV_VAR"
21+
target = ["production", "preview"]
22+
}
23+
24+
resource "vercel_project" "example" {
25+
name = "example"
26+
}
27+
28+
resource "vercel_shared_environment_variable_project_link" "example" {
29+
shared_environment_variable_id = data.vercel_shared_environment_variable.example.id
30+
project_id = vercel_project.example.id
31+
}
32+
```
33+
34+
<!-- schema generated by tfplugindocs -->
35+
## Schema
36+
37+
### Required
38+
39+
- `project_id` (String) The ID of the Vercel project.
40+
- `shared_environment_variable_id` (String) The ID of the shared environment variable.
41+
42+
### Optional
43+
44+
- `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.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
data "vercel_shared_environment_variable" "example" {
2+
key = "EXAMPLE_ENV_VAR"
3+
target = ["production", "preview"]
4+
}
5+
6+
resource "vercel_project" "example" {
7+
name = "example"
8+
}
9+
10+
resource "vercel_shared_environment_variable_project_link" "example" {
11+
shared_environment_variable_id = data.vercel_shared_environment_variable.example.id
12+
project_id = vercel_project.example.id
13+
}

vercel/provider.go

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
1212
"github.com/hashicorp/terraform-plugin-framework/resource"
1313
"github.com/hashicorp/terraform-plugin-framework/types"
14+
1415
"github.com/vercel/terraform-provider-vercel/v2/client"
1516
)
1617

@@ -72,6 +73,7 @@ func (p *vercelProvider) Resources(_ context.Context) []func() resource.Resource
7273
newProjectMembersResource,
7374
newProjectResource,
7475
newSharedEnvironmentVariableResource,
76+
newSharedEnvironmentVariableProjectLinkResource,
7577
newTeamConfigResource,
7678
newTeamMemberResource,
7779
newWebhookResource,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
package vercel
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"slices"
7+
8+
"github.com/hashicorp/terraform-plugin-framework/resource"
9+
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
10+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
11+
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
12+
"github.com/hashicorp/terraform-plugin-framework/types"
13+
"github.com/hashicorp/terraform-plugin-log/tflog"
14+
15+
"github.com/vercel/terraform-provider-vercel/v2/client"
16+
)
17+
18+
var (
19+
_ resource.Resource = &sharedEnvironmentVariableProjectLinkResource{}
20+
_ resource.ResourceWithConfigure = &sharedEnvironmentVariableProjectLinkResource{}
21+
)
22+
23+
func newSharedEnvironmentVariableProjectLinkResource() resource.Resource {
24+
return &sharedEnvironmentVariableProjectLinkResource{}
25+
}
26+
27+
type sharedEnvironmentVariableProjectLinkResource struct {
28+
client *client.Client
29+
}
30+
31+
func (r *sharedEnvironmentVariableProjectLinkResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
32+
resp.TypeName = req.ProviderTypeName + "_shared_environment_variable_project_link"
33+
}
34+
35+
func (r *sharedEnvironmentVariableProjectLinkResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
36+
// Prevent panic if the provider has not been configured.
37+
if req.ProviderData == nil {
38+
return
39+
}
40+
41+
client, ok := req.ProviderData.(*client.Client)
42+
if !ok {
43+
resp.Diagnostics.AddError(
44+
"Unexpected Resource Configure Type",
45+
fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
46+
)
47+
return
48+
}
49+
50+
r.client = client
51+
}
52+
53+
func (r *sharedEnvironmentVariableProjectLinkResource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
54+
resp.Schema = schema.Schema{
55+
Description: `Links a project to a Shared Environment Variable.
56+
57+
~> This resource is intended to be used alongside a vercel_shared_environment_variable Data Source, not the Resource. The Resource also defines which projects to link to the shared environment variable, and using both vercel_shared_environment_variable and vercel_shared_environment_variable_project_link together results in undefined behavior.`,
58+
Attributes: map[string]schema.Attribute{
59+
"shared_environment_variable_id": schema.StringAttribute{
60+
Required: true,
61+
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
62+
Description: "The ID of the shared environment variable.",
63+
},
64+
"project_id": schema.StringAttribute{
65+
Required: true,
66+
Description: "The ID of the Vercel project.",
67+
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
68+
},
69+
"team_id": schema.StringAttribute{
70+
Optional: true,
71+
Computed: true,
72+
Description: "The ID of the Vercel team. Required when configuring a team resource if a default team has not been set in the provider.",
73+
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplaceIfConfigured(), stringplanmodifier.UseStateForUnknown()},
74+
},
75+
},
76+
}
77+
}
78+
79+
type SharedEnvironmentVariableProjectLink struct {
80+
SharedEnvironmentVariableID types.String `tfsdk:"shared_environment_variable_id"`
81+
ProjectID types.String `tfsdk:"project_id"`
82+
TeamID types.String `tfsdk:"team_id"`
83+
}
84+
85+
func (e *SharedEnvironmentVariableProjectLink) toUpdateSharedEnvironmentVariableRequest(ctx context.Context, link bool) (req client.UpdateSharedEnvironmentVariableRequest, ok bool) {
86+
upd := client.UpdateSharedEnvironmentVariableRequestProjectIDUpdates{}
87+
88+
if link {
89+
upd.Link = []string{e.ProjectID.ValueString()}
90+
} else {
91+
upd.Unlink = []string{e.ProjectID.ValueString()}
92+
}
93+
94+
return client.UpdateSharedEnvironmentVariableRequest{
95+
TeamID: e.TeamID.ValueString(),
96+
EnvID: e.SharedEnvironmentVariableID.ValueString(),
97+
ProjectIDUpdates: upd,
98+
}, true
99+
}
100+
101+
func (r *sharedEnvironmentVariableProjectLinkResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
102+
var plan SharedEnvironmentVariableProjectLink
103+
diags := req.Plan.Get(ctx, &plan)
104+
resp.Diagnostics.Append(diags...)
105+
if resp.Diagnostics.HasError() {
106+
return
107+
}
108+
109+
request, ok := plan.toUpdateSharedEnvironmentVariableRequest(ctx, true)
110+
if !ok {
111+
return
112+
}
113+
114+
response, err := r.client.UpdateSharedEnvironmentVariable(ctx, request)
115+
if err != nil {
116+
resp.Diagnostics.AddError(
117+
"Error linking project to shared environment variable",
118+
"Could not link project to shared environment variable, unexpected error: "+err.Error(),
119+
)
120+
return
121+
}
122+
123+
result := SharedEnvironmentVariableProjectLink{
124+
TeamID: types.StringValue(response.TeamID),
125+
SharedEnvironmentVariableID: types.StringValue(response.ID),
126+
ProjectID: plan.ProjectID,
127+
}
128+
129+
tflog.Info(ctx, "linked shared environment variable to project", map[string]interface{}{
130+
"team_id": result.TeamID.ValueString(),
131+
"shared_environment_variable_id": result.SharedEnvironmentVariableID.ValueString(),
132+
"project_id": result.ProjectID.ValueString(),
133+
})
134+
135+
diags = resp.State.Set(ctx, result)
136+
resp.Diagnostics.Append(diags...)
137+
if resp.Diagnostics.HasError() {
138+
return
139+
}
140+
}
141+
142+
func (r *sharedEnvironmentVariableProjectLinkResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
143+
var state SharedEnvironmentVariableProjectLink
144+
diags := req.State.Get(ctx, &state)
145+
resp.Diagnostics.Append(diags...)
146+
if resp.Diagnostics.HasError() {
147+
return
148+
}
149+
150+
response, err := r.client.GetSharedEnvironmentVariable(ctx, state.TeamID.ValueString(), state.SharedEnvironmentVariableID.ValueString())
151+
if err != nil {
152+
resp.Diagnostics.AddError(
153+
"Error reading shared environment variable",
154+
"Could not read shared environment variable, unexpected error: "+err.Error(),
155+
)
156+
return
157+
}
158+
159+
if !slices.Contains(response.ProjectIDs, state.ProjectID.ValueString()) {
160+
tflog.Info(ctx, "failed to read shared environment variable for linked project", map[string]interface{}{
161+
"team_id": state.TeamID.ValueString(),
162+
"shared_environment_variable_id": state.SharedEnvironmentVariableID.ValueString(),
163+
"project_id": state.ProjectID.ValueString(),
164+
})
165+
166+
// not found, so replace state
167+
resp.State.RemoveResource(ctx)
168+
return
169+
}
170+
171+
result := SharedEnvironmentVariableProjectLink{
172+
TeamID: types.StringValue(response.TeamID),
173+
SharedEnvironmentVariableID: types.StringValue(response.ID),
174+
ProjectID: state.ProjectID,
175+
}
176+
tflog.Info(ctx, "read shared environment variable for linked project", map[string]interface{}{
177+
"team_id": result.TeamID.ValueString(),
178+
"shared_environment_variable_id": result.SharedEnvironmentVariableID.ValueString(),
179+
"project_id": result.ProjectID.ValueString(),
180+
})
181+
diags = resp.State.Set(ctx, result)
182+
resp.Diagnostics.Append(diags...)
183+
}
184+
185+
func (r *sharedEnvironmentVariableProjectLinkResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
186+
resp.Diagnostics.AddError("Linking should always be recreated", "Something incorrectly caused an Update, this should always be recreated instead of updated.")
187+
}
188+
189+
func (r *sharedEnvironmentVariableProjectLinkResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
190+
var plan SharedEnvironmentVariableProjectLink
191+
diags := req.State.Get(ctx, &plan)
192+
resp.Diagnostics.Append(diags...)
193+
if resp.Diagnostics.HasError() {
194+
return
195+
}
196+
197+
request, ok := plan.toUpdateSharedEnvironmentVariableRequest(ctx, false)
198+
if !ok {
199+
return
200+
}
201+
202+
response, err := r.client.UpdateSharedEnvironmentVariable(ctx, request)
203+
if err != nil {
204+
resp.Diagnostics.AddError(
205+
"Error unlinking project from shared environment variable",
206+
"Could not unlink project from shared environment variable, unexpected error: "+err.Error(),
207+
)
208+
return
209+
}
210+
211+
result := SharedEnvironmentVariableProjectLink{
212+
TeamID: types.StringValue(response.TeamID),
213+
SharedEnvironmentVariableID: types.StringValue(response.ID),
214+
ProjectID: plan.ProjectID,
215+
}
216+
217+
tflog.Info(ctx, "project unlinked from shared environment", map[string]interface{}{
218+
"team_id": result.TeamID.ValueString(),
219+
"shared_environment_variable_id": result.SharedEnvironmentVariableID.ValueString(),
220+
"project_id": result.ProjectID.ValueString(),
221+
})
222+
}

0 commit comments

Comments
 (0)