diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8c3548ec..008e0100 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -106,7 +106,7 @@ jobs: env: TF_ACC: "true" VERCEL_API_TOKEN: ${{ secrets.VERCEL_API_TOKEN }} - VERCEL_TERRAFORM_TESTING_TEAM: "team_RvxIb1z0pi9RSsQ13p3ES4cK" + VERCEL_TERRAFORM_TESTING_TEAM: "team_GwBFaTRF7juuJfO2jZzzKRgc" VERCEL_TERRAFORM_TESTING_GITHUB_REPO: "dglsparsons/test" VERCEL_TERRAFORM_TESTING_GITLAB_REPO: "dglsparsons/test" VERCEL_TERRAFORM_TESTING_BITBUCKET_REPO: "dglsparsons-test/test" diff --git a/client/project.go b/client/project.go index 2e42ca3b..e5633e85 100644 --- a/client/project.go +++ b/client/project.go @@ -224,12 +224,14 @@ type ResourceConfigResponse struct { FunctionDefaultMemoryType *string `json:"functionDefaultMemoryType"` FunctionDefaultTimeout *int64 `json:"functionDefaultTimeout"` Fluid bool `json:"fluid"` + ElasticConcurrencyEnabled bool `json:"elasticConcurrencyEnabled"` } type ResourceConfig struct { FunctionDefaultMemoryType *string `json:"functionDefaultMemoryType,omitempty"` FunctionDefaultTimeout *int64 `json:"functionDefaultTimeout,omitempty"` Fluid *bool `json:"fluid,omitempty"` + ElasticConcurrencyEnabled *bool `json:"elasticConcurrencyEnabled,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 8e463e7c..96cd0938 100644 --- a/docs/data-sources/project.md +++ b/docs/data-sources/project.md @@ -33,6 +33,7 @@ data "vercel_project" "example" { ### Optional +- `on_demand_concurrent_builds` (Boolean) Instantly scale build capacity to skip the queue, even if all build slots are in use. You can also choose a larger build machine; charges apply per minute if it exceeds your team's default. - `team_id` (String) The team ID the project exists beneath. Required when configuring a team resource if a default team has not been set in the provider. ### Read-Only diff --git a/docs/resources/project.md b/docs/resources/project.md index d42fb4f7..9546b27a 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -75,6 +75,7 @@ resource "vercel_project" "example" { - `install_command` (String) The install command for this project. If omitted, this value will be automatically detected. - `node_version` (String) The version of Node.js that is used in the Build Step and for Serverless Functions. A new Deployment is required for your changes to take effect. - `oidc_token_config` (Attributes) Configuration for OpenID Connect (OIDC) tokens. (see [below for nested schema](#nestedatt--oidc_token_config)) +- `on_demand_concurrent_builds` (Boolean) Instantly scale build capacity to skip the queue, even if all build slots are in use. You can also choose a larger build machine; charges apply per minute if it exceeds your team's default. - `options_allowlist` (Attributes) Disable Deployment Protection for CORS preflight `OPTIONS` requests for a list of paths. (see [below for nested schema](#nestedatt--options_allowlist)) - `output_directory` (String) The output directory of the project. If omitted, this value will be automatically detected. - `password_protection` (Attributes) Ensures visitors of your Preview Deployments must enter a password in order to gain access. (see [below for nested schema](#nestedatt--password_protection)) diff --git a/vercel/data_source_project.go b/vercel/data_source_project.go index b083fcc0..ada0d1d5 100644 --- a/vercel/data_source_project.go +++ b/vercel/data_source_project.go @@ -388,6 +388,11 @@ For more detailed information, please see the [Vercel documentation](https://ver }, }, }, + "on_demand_concurrent_builds": schema.BoolAttribute{ + Description: "Instantly scale build capacity to skip the queue, even if all build slots are in use. You can also choose a larger build machine; charges apply per minute if it exceeds your team's default.", + Optional: true, + Computed: true, + }, }, } } @@ -431,6 +436,7 @@ type ProjectDataSource struct { SkewProtection types.String `tfsdk:"skew_protection"` ResourceConfig types.Object `tfsdk:"resource_config"` NodeVersion types.String `tfsdk:"node_version"` + OnDemandConcurrentBuilds types.Bool `tfsdk:"on_demand_concurrent_builds"` } func convertResponseToProjectDataSource(ctx context.Context, response client.ProjectResponse, plan Project, environmentVariables []client.EnvironmentVariable) (ProjectDataSource, error) { @@ -496,6 +502,7 @@ func convertResponseToProjectDataSource(ctx context.Context, response client.Pro SkewProtection: project.SkewProtection, ResourceConfig: project.ResourceConfig, NodeVersion: project.NodeVersion, + OnDemandConcurrentBuilds: project.OnDemandConcurrentBuilds, }, nil } diff --git a/vercel/data_source_project_test.go b/vercel/data_source_project_test.go index eb6bfdbe..2d98b506 100644 --- a/vercel/data_source_project_test.go +++ b/vercel/data_source_project_test.go @@ -58,6 +58,7 @@ func TestAcc_ProjectDataSource(t *testing.T) { resource.TestCheckResourceAttr("data.vercel_project.test", "resource_config.function_default_timeout", "30"), resource.TestCheckResourceAttr("data.vercel_project.test", "oidc_token_config.enabled", "true"), resource.TestCheckResourceAttr("data.vercel_project.test", "oidc_token_config.issuer_mode", "team"), + resource.TestCheckResourceAttr("data.vercel_project.test", "on_demand_concurrent_builds", "true"), ), }, }, @@ -140,6 +141,7 @@ resource "vercel_project" "test" { enabled = true issuer_mode = "team" } + on_demand_concurrent_builds = true } data "vercel_project" "test" { diff --git a/vercel/data_source_team_config_test.go b/vercel/data_source_team_config_test.go index 3c937356..3aaedf04 100644 --- a/vercel/data_source_team_config_test.go +++ b/vercel/data_source_team_config_test.go @@ -35,7 +35,7 @@ func TestAcc_TeamConfigDataSource(t *testing.T) { func testAccVercelTeamConfigDataSource(teamID string) string { return fmt.Sprintf(` data "vercel_team_config" "test" { - id = "%s" // Replace with a valid team ID + id = "%s" } `, teamID) } diff --git a/vercel/data_source_team_member.go b/vercel/data_source_team_member.go index 1af8e97c..05c8c81e 100644 --- a/vercel/data_source_team_member.go +++ b/vercel/data_source_team_member.go @@ -3,6 +3,7 @@ package vercel import ( "context" "fmt" + "time" "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -124,9 +125,23 @@ func (d *teamMemberDataSource) Read(ctx context.Context, req datasource.ReadRequ return } - response, err := d.client.GetTeamMember(ctx, client.GetTeamMemberRequest{ - TeamID: config.TeamID.ValueString(), - UserID: config.UserID.ValueString(), + var response client.TeamMember + getRetry := Retry{ + Base: 200 * time.Millisecond, + Attempts: 7, + } + err := getRetry.Do(func(attempt int) (shouldRetry bool, err error) { + response, err = d.client.GetTeamMember(ctx, client.GetTeamMemberRequest{ + TeamID: config.TeamID.ValueString(), + UserID: config.UserID.ValueString(), + }) + if client.NotFound(err) { + return true, err + } + if err != nil { + return true, fmt.Errorf("unexpected error: %w", err) + } + return false, err }) if err != nil { resp.Diagnostics.AddError( diff --git a/vercel/data_source_team_member_test.go b/vercel/data_source_team_member_test.go index e80dcecf..eae9416a 100644 --- a/vercel/data_source_team_member_test.go +++ b/vercel/data_source_team_member_test.go @@ -27,7 +27,7 @@ func testAccTeamMemberDataSourceConfig(teamIDConfig string, user string) string return fmt.Sprintf(` resource "vercel_team_member" "test" { %[1]s - user_id = "%s" + user_id = "%[2]s" role = "MEMBER" } diff --git a/vercel/resource_project.go b/vercel/resource_project.go index f6347331..f96cec14 100644 --- a/vercel/resource_project.go +++ b/vercel/resource_project.go @@ -573,6 +573,12 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ }, }, }, + "on_demand_concurrent_builds": schema.BoolAttribute{ + Description: "Instantly scale build capacity to skip the queue, even if all build slots are in use. You can also choose a larger build machine; charges apply per minute if it exceeds your team's default.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, + }, }, } } @@ -624,6 +630,7 @@ type Project struct { EnableAffectedProjectsDeployments types.Bool `tfsdk:"enable_affected_projects_deployments"` SkewProtection types.String `tfsdk:"skew_protection"` ResourceConfig types.Object `tfsdk:"resource_config"` + OnDemandConcurrentBuilds types.Bool `tfsdk:"on_demand_concurrent_builds"` } type GitComments struct { @@ -743,7 +750,7 @@ func (p *Project) toCreateProjectRequest(ctx context.Context, envs []Environment PublicSource: p.PublicSource.ValueBoolPointer(), RootDirectory: p.RootDirectory.ValueStringPointer(), ServerlessFunctionRegion: p.ServerlessFunctionRegion.ValueString(), - ResourceConfig: resourceConfig.toClientResourceConfig(), + ResourceConfig: resourceConfig.toClientResourceConfig(p.OnDemandConcurrentBuilds), EnablePreviewFeedback: oneBoolPointer(p.EnablePreviewFeedback, p.PreviewComments), EnableProductionFeedback: p.EnableProductionFeedback.ValueBoolPointer(), }, diags @@ -824,7 +831,7 @@ func (p *Project) toUpdateProjectRequest(ctx context.Context, oldName string) (r DirectoryListing: p.DirectoryListing.ValueBool(), SkewProtectionMaxAge: toSkewProtectionAge(p.SkewProtection), GitComments: gc.toUpdateProjectRequest(), - ResourceConfig: resourceConfig.toClientResourceConfig(), + ResourceConfig: resourceConfig.toClientResourceConfig(p.OnDemandConcurrentBuilds), NodeVersion: p.NodeVersion.ValueString(), }, nil } @@ -1076,7 +1083,7 @@ func (p *Project) resourceConfig(ctx context.Context) (rc *ResourceConfig, diags return rc, diags } -func (r *ResourceConfig) toClientResourceConfig() *client.ResourceConfig { +func (r *ResourceConfig) toClientResourceConfig(onDemandConcurrentBuilds types.Bool) *client.ResourceConfig { if r == nil { return nil } @@ -1091,6 +1098,9 @@ func (r *ResourceConfig) toClientResourceConfig() *client.ResourceConfig { if !r.Fluid.IsUnknown() { resourceConfig.Fluid = r.Fluid.ValueBoolPointer() } + if !onDemandConcurrentBuilds.IsUnknown() { + resourceConfig.ElasticConcurrencyEnabled = onDemandConcurrentBuilds.ValueBoolPointer() + } return resourceConfig } @@ -1461,6 +1471,7 @@ func convertResponseToProject(ctx context.Context, response client.ProjectRespon GitComments: gitComments, ResourceConfig: resourceConfig, NodeVersion: types.StringValue(response.NodeVersion), + OnDemandConcurrentBuilds: types.BoolValue(response.ResourceConfig.ElasticConcurrencyEnabled), }, nil } diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go index 85a6a435..1fe1aaf2 100644 --- a/vercel/resource_project_test.go +++ b/vercel/resource_project_test.go @@ -82,6 +82,7 @@ func TestAcc_Project(t *testing.T) { resource.TestCheckResourceAttr("vercel_project.test", "oidc_token_config.issuer_mode", "team"), resource.TestCheckResourceAttr("vercel_project.test", "resource_config.function_default_cpu_type", "standard"), resource.TestCheckResourceAttr("vercel_project.test", "resource_config.function_default_timeout", "60"), + resource.TestCheckResourceAttr("vercel_project.test", "on_demand_concurrent_builds", "true"), ), }, // Update testing @@ -98,6 +99,7 @@ func TestAcc_Project(t *testing.T) { resource.TestCheckResourceAttr("vercel_project.test", "preview_comments", "false"), resource.TestCheckResourceAttr("vercel_project.test", "enable_preview_feedback", "false"), resource.TestCheckResourceAttr("vercel_project.test", "enable_production_feedback", "true"), + resource.TestCheckResourceAttr("vercel_project.test", "on_demand_concurrent_builds", "false"), ), }, // Test mutual exclusivity validation @@ -353,7 +355,7 @@ func TestAcc_ProjectWithVercelAuthAndPasswordProtectionAndTrustedIps(t *testing. "note": "notey note", }), resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "trusted_ips.deployment_type", "all_deployments"), - resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "trusted_ips.protection_mode", "trusted_ip_optional"), + resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "trusted_ips.protection_mode", "trusted_ip_required"), resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "options_allowlist.paths.#", "1"), resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "options_allowlist.paths.0.value", "/foo"), resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "protection_bypass_for_automation", "true"), @@ -414,7 +416,7 @@ func TestAcc_ProjectWithVercelAuthAndPasswordProtectionAndTrustedIps(t *testing. "note": "notey notey", }), resource.TestCheckResourceAttr("vercel_project.enabled_to_update", "trusted_ips.deployment_type", "all_deployments"), - resource.TestCheckResourceAttr("vercel_project.enabled_to_update", "trusted_ips.protection_mode", "trusted_ip_optional"), + resource.TestCheckResourceAttr("vercel_project.enabled_to_update", "trusted_ips.protection_mode", "trusted_ip_required"), resource.TestCheckResourceAttr("vercel_project.enabled_to_update", "protection_bypass_for_automation", "false"), resource.TestCheckResourceAttr("vercel_project.enabled_to_update", "options_allowlist.paths.#", "1"), resource.TestCheckResourceAttr("vercel_project.enabled_to_update", "options_allowlist.paths.0.value", "/bar"), @@ -651,6 +653,7 @@ resource "vercel_project" "test" { sensitive = true } ] + on_demand_concurrent_builds = false enable_preview_feedback = false enable_production_feedback = true } @@ -708,7 +711,7 @@ resource "vercel_project" "enabled_to_start" { } ] deployment_type = "all_deployments" - protection_mode = "trusted_ip_optional" + protection_mode = "trusted_ip_required" } options_allowlist = { paths = [ @@ -814,7 +817,7 @@ resource "vercel_project" "enabled_to_update" { } ] deployment_type = "all_deployments" - protection_mode = "trusted_ip_optional" + protection_mode = "trusted_ip_required" } options_allowlist = { paths = [ @@ -1097,6 +1100,7 @@ resource "vercel_project" "test" { sensitive = true } ] + on_demand_concurrent_builds = true } `, projectSuffix, teamID) } diff --git a/vercel/resource_team_config_test.go b/vercel/resource_team_config_test.go index 85a95c0a..6d6fed28 100644 --- a/vercel/resource_team_config_test.go +++ b/vercel/resource_team_config_test.go @@ -15,22 +15,18 @@ func TestAcc_TeamConfig(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccVercelTeamConfigBasic(testTeam(t)), - // Added since vercel_team_config schema version upgraded - ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", "vercel-terraform-test"), - resource.TestCheckResourceAttr(resourceName, "slug", "vercel-terraform-test-ci"), + resource.TestCheckResourceAttr(resourceName, "name", "Vercel Terraform Testing"), + resource.TestCheckResourceAttr(resourceName, "slug", "terraform-testing-vtest314"), resource.TestCheckResourceAttrSet(resourceName, "id"), ), }, { Config: testAccVercelTeamConfigUpdated(testTeam(t)), - // Added since vercel_team_config schema version upgraded - ExpectNonEmptyPlan: true, Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet(resourceName, "id"), - resource.TestCheckResourceAttr(resourceName, "name", "vercel-terraform-test-ci"), - resource.TestCheckResourceAttr(resourceName, "slug", "vercel-terraform-test-ci"), + resource.TestCheckResourceAttr(resourceName, "name", "Vercel Terraform Testing o_o"), + resource.TestCheckResourceAttr(resourceName, "slug", "terraform-testing-vtest314"), resource.TestCheckResourceAttr(resourceName, "description", "Vercel Terraform Testing"), resource.TestCheckResourceAttr(resourceName, "sensitive_environment_variable_policy", "off"), resource.TestCheckResourceAttr(resourceName, "remote_caching.enabled", "true"), @@ -47,8 +43,8 @@ func TestAcc_TeamConfig(t *testing.T) { func testAccVercelTeamConfigBasic(teamID string) string { return fmt.Sprintf(` resource "vercel_team_config" "test" { - id = "%s" // Replace with a valid team ID - name = "vercel-terraform-test" + id = "%s" + name = "Vercel Terraform Testing" } `, teamID) } @@ -62,8 +58,8 @@ data "vercel_file" "test" { resource "vercel_team_config" "test" { id = "%s" avatar = data.vercel_file.test.file - name = "vercel-terraform-test-ci" - slug = "vercel-terraform-test-ci" + name = "Vercel Terraform Testing o_o" + slug = "terraform-testing-vtest314" description = "Vercel Terraform Testing" sensitive_environment_variable_policy = "off" remote_caching = { diff --git a/vercel/resource_team_member.go b/vercel/resource_team_member.go index 1c76bcbe..bf28a143 100644 --- a/vercel/resource_team_member.go +++ b/vercel/resource_team_member.go @@ -404,6 +404,9 @@ func (r *teamMemberResource) Create(ctx context.Context, req resource.CreateRequ if client.NotFound(err) { return true, err } + if err != nil { + return true, fmt.Errorf("unexpected error: %w", err) + } teamMember := convertResponseToTeamMember(response, plan) if teamMember.Role != plan.Role { tflog.Error(ctx, "Role has not yet propagated", map[string]any{})