-
Notifications
You must be signed in to change notification settings - Fork 31
Add resource for adding single projects to shared environment variables #275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1c387c0
27d41af
77f553a
71564aa
8d35859
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
--- | ||
# generated by https://github.com/hashicorp/terraform-plugin-docs | ||
page_title: "vercel_shared_environment_variable_project_link Resource - terraform-provider-vercel" | ||
subcategory: "" | ||
description: |- | ||
Links a project to a Shared Environment Variable. | ||
~> 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. | ||
--- | ||
|
||
# vercel_shared_environment_variable_project_link (Resource) | ||
|
||
Links a project to a Shared Environment Variable. | ||
|
||
~> 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. | ||
|
||
## Example Usage | ||
|
||
```terraform | ||
data "vercel_shared_environment_variable" "example" { | ||
key = "EXAMPLE_ENV_VAR" | ||
target = ["production", "preview"] | ||
} | ||
|
||
resource "vercel_project" "example" { | ||
name = "example" | ||
} | ||
|
||
resource "vercel_shared_environment_variable_project_link" "example" { | ||
shared_environment_variable_id = data.vercel_shared_environment_variable.example.id | ||
project_id = vercel_project.example.id | ||
} | ||
``` | ||
|
||
<!-- schema generated by tfplugindocs --> | ||
## Schema | ||
|
||
### Required | ||
|
||
- `project_id` (String) The ID of the Vercel project. | ||
- `shared_environment_variable_id` (String) The ID of the shared environment variable. | ||
|
||
### Optional | ||
|
||
- `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 number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
data "vercel_shared_environment_variable" "example" { | ||
key = "EXAMPLE_ENV_VAR" | ||
target = ["production", "preview"] | ||
} | ||
|
||
resource "vercel_project" "example" { | ||
name = "example" | ||
} | ||
|
||
resource "vercel_shared_environment_variable_project_link" "example" { | ||
shared_environment_variable_id = data.vercel_shared_environment_variable.example.id | ||
project_id = vercel_project.example.id | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
package vercel | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"slices" | ||
|
||
"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/v2/client" | ||
) | ||
|
||
var ( | ||
_ resource.Resource = &sharedEnvironmentVariableProjectLinkResource{} | ||
_ resource.ResourceWithConfigure = &sharedEnvironmentVariableProjectLinkResource{} | ||
) | ||
|
||
func newSharedEnvironmentVariableProjectLinkResource() resource.Resource { | ||
return &sharedEnvironmentVariableProjectLinkResource{} | ||
} | ||
|
||
type sharedEnvironmentVariableProjectLinkResource struct { | ||
client *client.Client | ||
} | ||
|
||
func (r *sharedEnvironmentVariableProjectLinkResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { | ||
resp.TypeName = req.ProviderTypeName + "_shared_environment_variable_project_link" | ||
} | ||
|
||
func (r *sharedEnvironmentVariableProjectLinkResource) 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 | ||
} | ||
|
||
func (r *sharedEnvironmentVariableProjectLinkResource) Schema(_ context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { | ||
resp.Schema = schema.Schema{ | ||
Description: `Links a project to a Shared Environment Variable. | ||
|
||
~> 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.`, | ||
Attributes: map[string]schema.Attribute{ | ||
"shared_environment_variable_id": schema.StringAttribute{ | ||
Required: true, | ||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, | ||
Description: "The ID of the shared environment variable.", | ||
}, | ||
"project_id": schema.StringAttribute{ | ||
Required: true, | ||
Description: "The ID of the Vercel project.", | ||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, | ||
}, | ||
"team_id": schema.StringAttribute{ | ||
Optional: true, | ||
Computed: true, | ||
Description: "The ID of the Vercel team. Required when configuring a team resource if a default team has not been set in the provider.", | ||
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplaceIfConfigured(), stringplanmodifier.UseStateForUnknown()}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
type SharedEnvironmentVariableProjectLink struct { | ||
SharedEnvironmentVariableID types.String `tfsdk:"shared_environment_variable_id"` | ||
ProjectID types.String `tfsdk:"project_id"` | ||
TeamID types.String `tfsdk:"team_id"` | ||
} | ||
|
||
func (e *SharedEnvironmentVariableProjectLink) toUpdateSharedEnvironmentVariableRequest(ctx context.Context, link bool) (req client.UpdateSharedEnvironmentVariableRequest, ok bool) { | ||
upd := client.UpdateSharedEnvironmentVariableRequestProjectIDUpdates{} | ||
|
||
if link { | ||
upd.Link = []string{e.ProjectID.ValueString()} | ||
} else { | ||
upd.Unlink = []string{e.ProjectID.ValueString()} | ||
} | ||
|
||
return client.UpdateSharedEnvironmentVariableRequest{ | ||
TeamID: e.TeamID.ValueString(), | ||
EnvID: e.SharedEnvironmentVariableID.ValueString(), | ||
ProjectIDUpdates: upd, | ||
}, true | ||
} | ||
|
||
func (r *sharedEnvironmentVariableProjectLinkResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { | ||
var plan SharedEnvironmentVariableProjectLink | ||
diags := req.Plan.Get(ctx, &plan) | ||
resp.Diagnostics.Append(diags...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
request, ok := plan.toUpdateSharedEnvironmentVariableRequest(ctx, true) | ||
if !ok { | ||
return | ||
} | ||
|
||
response, err := r.client.UpdateSharedEnvironmentVariable(ctx, request) | ||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
"Error linking project to shared environment variable", | ||
"Could not link project to shared environment variable, unexpected error: "+err.Error(), | ||
) | ||
return | ||
} | ||
|
||
result := SharedEnvironmentVariableProjectLink{ | ||
TeamID: types.StringValue(response.TeamID), | ||
SharedEnvironmentVariableID: types.StringValue(response.ID), | ||
ProjectID: plan.ProjectID, | ||
} | ||
|
||
tflog.Info(ctx, "linked shared environment variable to project", map[string]interface{}{ | ||
"team_id": result.TeamID.ValueString(), | ||
"shared_environment_variable_id": result.SharedEnvironmentVariableID.ValueString(), | ||
"project_id": result.ProjectID.ValueString(), | ||
}) | ||
|
||
diags = resp.State.Set(ctx, result) | ||
resp.Diagnostics.Append(diags...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
} | ||
|
||
func (r *sharedEnvironmentVariableProjectLinkResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { | ||
var state SharedEnvironmentVariableProjectLink | ||
diags := req.State.Get(ctx, &state) | ||
resp.Diagnostics.Append(diags...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
response, err := r.client.GetSharedEnvironmentVariable(ctx, state.TeamID.ValueString(), state.SharedEnvironmentVariableID.ValueString()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to handle 404 here, and remove state if the entire shared environment variable is gone? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so, because if we are Reading it we expect it to exist, and want to detect when it doesn't (e.g. drift) if the shared env var is just gone, then I think that's an error that requires manual intervention rather than trying to recreate the link. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh, okay, because this has to be used with a data source, right? |
||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
"Error reading shared environment variable", | ||
"Could not read shared environment variable, unexpected error: "+err.Error(), | ||
) | ||
return | ||
} | ||
|
||
if !slices.Contains(response.ProjectIDs, state.ProjectID.ValueString()) { | ||
tflog.Info(ctx, "failed to read shared environment variable for linked project", map[string]interface{}{ | ||
"team_id": state.TeamID.ValueString(), | ||
"shared_environment_variable_id": state.SharedEnvironmentVariableID.ValueString(), | ||
"project_id": state.ProjectID.ValueString(), | ||
}) | ||
|
||
// not found, so replace state | ||
resp.State.RemoveResource(ctx) | ||
return | ||
} | ||
|
||
result := SharedEnvironmentVariableProjectLink{ | ||
TeamID: types.StringValue(response.TeamID), | ||
SharedEnvironmentVariableID: types.StringValue(response.ID), | ||
ProjectID: state.ProjectID, | ||
} | ||
tflog.Info(ctx, "read shared environment variable for linked project", map[string]interface{}{ | ||
"team_id": result.TeamID.ValueString(), | ||
"shared_environment_variable_id": result.SharedEnvironmentVariableID.ValueString(), | ||
"project_id": result.ProjectID.ValueString(), | ||
}) | ||
diags = resp.State.Set(ctx, result) | ||
resp.Diagnostics.Append(diags...) | ||
} | ||
|
||
func (r *sharedEnvironmentVariableProjectLinkResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { | ||
resp.Diagnostics.AddError("Linking should always be recreated", "Something incorrectly caused an Update, this should always be recreated instead of updated.") | ||
} | ||
|
||
func (r *sharedEnvironmentVariableProjectLinkResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { | ||
var plan SharedEnvironmentVariableProjectLink | ||
diags := req.State.Get(ctx, &plan) | ||
resp.Diagnostics.Append(diags...) | ||
if resp.Diagnostics.HasError() { | ||
return | ||
} | ||
|
||
request, ok := plan.toUpdateSharedEnvironmentVariableRequest(ctx, false) | ||
if !ok { | ||
return | ||
} | ||
|
||
response, err := r.client.UpdateSharedEnvironmentVariable(ctx, request) | ||
if err != nil { | ||
resp.Diagnostics.AddError( | ||
"Error unlinking project from shared environment variable", | ||
"Could not unlink project from shared environment variable, unexpected error: "+err.Error(), | ||
) | ||
return | ||
} | ||
|
||
result := SharedEnvironmentVariableProjectLink{ | ||
TeamID: types.StringValue(response.TeamID), | ||
SharedEnvironmentVariableID: types.StringValue(response.ID), | ||
ProjectID: plan.ProjectID, | ||
} | ||
|
||
tflog.Info(ctx, "project unlinked from shared environment", map[string]interface{}{ | ||
"team_id": result.TeamID.ValueString(), | ||
"shared_environment_variable_id": result.SharedEnvironmentVariableID.ValueString(), | ||
"project_id": result.ProjectID.ValueString(), | ||
}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another thought - should this have an Import, so you can terraform import team_xxx/env_yyy/prj_zzz ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hrmmmmmm.. maybe? I wouldn't really expect to need to import this, and a creation of an existing resource would just be a no-op. I'll be able to test this as I already have projects linked to shared env vars that I would want to put into terraform. |
||
} |
Uh oh!
There was an error while loading. Please reload this page.