From cdd769258fac9b4906313765028ea55ec3d27f70 Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Wed, 19 Mar 2025 15:44:16 +0000 Subject: [PATCH 1/2] Add support for setting Fluid on vercel_project resource --- client/project.go | 9 +++- docs/data-sources/project.md | 1 + docs/resources/project.md | 1 + vercel/data_source_project.go | 4 ++ vercel/resource_dns_record.go | 5 +- vercel/resource_project.go | 27 ++++++++-- vercel/resource_project_test.go | 73 +++++++++++++++++++++++++++ vercel/resource_project_validators.go | 47 +++++++++++++++++ 8 files changed, 160 insertions(+), 7 deletions(-) create mode 100644 vercel/resource_project_validators.go diff --git a/client/project.go b/client/project.go index 7104a016..126a5fc8 100644 --- a/client/project.go +++ b/client/project.go @@ -204,7 +204,7 @@ type ProjectResponse struct { GitComments *GitComments `json:"gitComments"` Security *Security `json:"security"` DeploymentExpiration *DeploymentExpiration `json:"deploymentExpiration"` - ResourceConfig *ResourceConfig `json:"resourceConfig"` + ResourceConfig *ResourceConfigResponse `json:"resourceConfig"` NodeVersion string `json:"nodeVersion"` } @@ -217,9 +217,16 @@ type Security struct { AttackModeEnabled bool `json:"attackModeEnabled"` } +type ResourceConfigResponse struct { + FunctionDefaultMemoryType *string `json:"functionDefaultMemoryType"` + FunctionDefaultTimeout *int64 `json:"functionDefaultTimeout"` + Fluid bool `json:"fluid"` +} + type ResourceConfig struct { FunctionDefaultMemoryType *string `json:"functionDefaultMemoryType,omitempty"` FunctionDefaultTimeout *int64 `json:"functionDefaultTimeout,omitempty"` + Fluid *bool `json:"fluid,omitempty"` } // GetProject retrieves information about an existing project from Vercel. diff --git a/docs/data-sources/project.md b/docs/data-sources/project.md index b100fffa..889d4764 100644 --- a/docs/data-sources/project.md +++ b/docs/data-sources/project.md @@ -158,6 +158,7 @@ Read-Only: Read-Only: +- `fluid` (Boolean) Enable fluid compute for your Vercel Functions to automatically manage concurrency and optimize performance. Vercel will handle the defaults to ensure the best experience for your workload. - `function_default_cpu_type` (String) The amount of CPU available to your Serverless Functions. Should be one of 'standard_legacy' (0.6vCPU), 'standard' (1vCPU) or 'performance' (1.7vCPUs). - `function_default_timeout` (Number) The default timeout for Serverless Functions. diff --git a/docs/resources/project.md b/docs/resources/project.md index 055727fc..cb3d4cca 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -193,6 +193,7 @@ Required: Optional: +- `fluid` (Boolean) Enable fluid compute for your Vercel Functions to automatically manage concurrency and optimize performance. Vercel will handle the defaults to ensure the best experience for your workload. - `function_default_cpu_type` (String) The amount of CPU available to your Serverless Functions. Should be one of 'standard_legacy' (0.6vCPU), 'standard' (1vCPU) or 'performance' (1.7vCPUs). - `function_default_timeout` (Number) The default timeout for Serverless Functions. diff --git a/vercel/data_source_project.go b/vercel/data_source_project.go index 33266a89..ec60dccb 100644 --- a/vercel/data_source_project.go +++ b/vercel/data_source_project.go @@ -373,6 +373,10 @@ For more detailed information, please see the [Vercel documentation](https://ver Description: "The default timeout for Serverless Functions.", Computed: true, }, + "fluid": schema.BoolAttribute{ + Description: "Enable fluid compute for your Vercel Functions to automatically manage concurrency and optimize performance. Vercel will handle the defaults to ensure the best experience for your workload.", + Computed: true, + }, }, }, }, diff --git a/vercel/resource_dns_record.go b/vercel/resource_dns_record.go index ad80121c..a8b169f7 100644 --- a/vercel/resource_dns_record.go +++ b/vercel/resource_dns_record.go @@ -20,8 +20,9 @@ import ( ) var ( - _ resource.Resource = &dnsRecordResource{} - _ resource.ResourceWithConfigure = &dnsRecordResource{} + _ resource.Resource = &dnsRecordResource{} + _ resource.ResourceWithConfigure = &dnsRecordResource{} + _ resource.ResourceWithValidateConfig = &dnsRecordResource{} ) func newDNSRecordResource() resource.Resource { diff --git a/vercel/resource_project.go b/vercel/resource_project.go index 9887e48c..f9a681e4 100644 --- a/vercel/resource_project.go +++ b/vercel/resource_project.go @@ -30,10 +30,11 @@ import ( ) var ( - _ resource.Resource = &projectResource{} - _ resource.ResourceWithConfigure = &projectResource{} - _ resource.ResourceWithImportState = &projectResource{} - _ resource.ResourceWithModifyPlan = &projectResource{} + _ resource.Resource = &projectResource{} + _ resource.ResourceWithConfigure = &projectResource{} + _ resource.ResourceWithImportState = &projectResource{} + _ resource.ResourceWithModifyPlan = &projectResource{} + _ resource.ResourceWithConfigValidators = &projectResource{} ) func newProjectResource() resource.Resource { @@ -543,12 +544,24 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ }, PlanModifiers: []planmodifier.Int64{int64planmodifier.UseStateForUnknown()}, }, + "fluid": schema.BoolAttribute{ + Description: "Enable fluid compute for your Vercel Functions to automatically manage concurrency and optimize performance. Vercel will handle the defaults to ensure the best experience for your workload.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, + }, }, }, }, } } +func (r *projectResource) ConfigValidators(ctx context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + &fluidComputeBasicCPUValidator{}, + } +} + // Project reflects the state terraform stores internally for a project. type Project struct { BuildCommand types.String `tfsdk:"build_command"` @@ -1003,12 +1016,14 @@ var resourceConfigAttrType = types.ObjectType{ AttrTypes: map[string]attr.Type{ "function_default_cpu_type": types.StringType, "function_default_timeout": types.Int64Type, + "fluid": types.BoolType, }, } type ResourceConfig struct { FunctionDefaultCPUType types.String `tfsdk:"function_default_cpu_type"` FunctionDefaultTimeout types.Int64 `tfsdk:"function_default_timeout"` + Fluid types.Bool `tfsdk:"fluid"` } func (p *Project) resourceConfig(ctx context.Context) (rc *ResourceConfig, diags diag.Diagnostics) { @@ -1031,6 +1046,9 @@ func (r *ResourceConfig) toClientResourceConfig() *client.ResourceConfig { if !r.FunctionDefaultTimeout.IsUnknown() { resourceConfig.FunctionDefaultTimeout = r.FunctionDefaultTimeout.ValueInt64Pointer() } + if !r.Fluid.IsUnknown() { + resourceConfig.Fluid = r.Fluid.ValueBoolPointer() + } return resourceConfig } @@ -1253,6 +1271,7 @@ func convertResponseToProject(ctx context.Context, response client.ProjectRespon resourceConfig = types.ObjectValueMust(resourceConfigAttrType.AttrTypes, map[string]attr.Value{ "function_default_cpu_type": types.StringPointerValue(response.ResourceConfig.FunctionDefaultMemoryType), "function_default_timeout": types.Int64PointerValue(response.ResourceConfig.FunctionDefaultTimeout), + "fluid": types.BoolValue(response.ResourceConfig.Fluid), }) } diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go index 32513840..e9264a24 100644 --- a/vercel/resource_project_test.go +++ b/vercel/resource_project_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "regexp" + "strings" "testing" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" @@ -103,6 +104,78 @@ func TestAcc_Project(t *testing.T) { }) } +func TestAccProject_FluidCompute(t *testing.T) { + projectSuffix := acctest.RandString(16) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + CheckDestroy: testAccProjectDestroy("vercel_project.test", testTeam()), + Steps: []resource.TestStep{ + { + // check we get a sensible error if fluid + invalid CPU combination. + Config: ` + resource "vercel_project" "test" { + name = "foo" + resource_config = { + fluid = true + function_default_cpu_type = "standard_legacy" + } + } + `, + ExpectError: regexp.MustCompile(strings.ReplaceAll("Fluid compute is only supported with the standard or performance CPU types.", " ", `\s*`)), + }, + { + // check creating a project with Fluid + Config: fmt.Sprintf(` + resource "vercel_project" "test" { + name = "test-acc-fluid-%[1]s" + %[2]s + + resource_config = { + fluid = true + } + } + `, projectSuffix, teamIDConfig()), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("vercel_project.test", "name", fmt.Sprintf("test-acc-fluid-%s", projectSuffix)), + resource.TestCheckResourceAttr("vercel_project.test", "resource_config.fluid", "true"), + ), + }, + { + // check updating Fluid on a project + Config: fmt.Sprintf(` + resource "vercel_project" "test" { + name = "test-acc-fluid-%[1]s" + %[2]s + + resource_config = { + fluid = false + } + } + `, projectSuffix, teamIDConfig()), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("vercel_project.test", "name", fmt.Sprintf("test-acc-fluid-%s", projectSuffix)), + resource.TestCheckResourceAttr("vercel_project.test", "resource_config.fluid", "false"), + ), + }, + { + // check new projects without fluid specified shows fluid as false + Config: fmt.Sprintf(` + resource "vercel_project" "test" { + name = "test-acc-fluid-disabled-%[1]s" + %[2]s + } + `, projectSuffix, teamIDConfig()), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("vercel_project.test", "name", fmt.Sprintf("test-acc-fluid-disabled-%s", projectSuffix)), + resource.TestCheckResourceAttr("vercel_project.test", "resource_config.fluid", "false"), + ), + }, + }, + }) +} + func TestAcc_ProjectAddingEnvAfterInitialCreation(t *testing.T) { projectSuffix := acctest.RandString(16) resource.Test(t, resource.TestCase{ diff --git a/vercel/resource_project_validators.go b/vercel/resource_project_validators.go new file mode 100644 index 00000000..6d08d42b --- /dev/null +++ b/vercel/resource_project_validators.go @@ -0,0 +1,47 @@ +package vercel + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +var _ resource.ConfigValidator = &fluidComputeBasicCPUValidator{} + +type fluidComputeBasicCPUValidator struct{} + +func (v *fluidComputeBasicCPUValidator) Description(ctx context.Context) string { + return "Validates that the CPU type is one of the allowed values for Vercel fluid compute." +} + +func (v *fluidComputeBasicCPUValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v *fluidComputeBasicCPUValidator) ValidateResource(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var project Project + diags := req.Config.Get(ctx, &project) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + resourceConfig, diags := project.resourceConfig(ctx) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + if resourceConfig == nil { + return + } + if !resourceConfig.Fluid.ValueBool() { + return + } + if resourceConfig.FunctionDefaultCPUType.ValueString() != "standard_legacy" { + return + } + + resp.Diagnostics.AddError( + "Error validating project fluid compute configuration", + "Fluid compute is only supported with the standard or performance CPU types.", + ) +} From afb99ef03f980abdf5814f9a3924c6c68691c87c Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Wed, 19 Mar 2025 15:52:31 +0000 Subject: [PATCH 2/2] Fix lint failure --- vercel/resource_project_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go index e9264a24..6e88ca7b 100644 --- a/vercel/resource_project_test.go +++ b/vercel/resource_project_test.go @@ -104,7 +104,7 @@ func TestAcc_Project(t *testing.T) { }) } -func TestAccProject_FluidCompute(t *testing.T) { +func TestAcc_ProjectFluidCompute(t *testing.T) { projectSuffix := acctest.RandString(16) resource.Test(t, resource.TestCase{