From dd607365b7fff33c395cec31e1a8cdc43810bf71 Mon Sep 17 00:00:00 2001 From: Douglas Harcourt Parsons Date: Thu, 25 Apr 2024 17:18:07 +0100 Subject: [PATCH] Add support for project fields This PR adds support on the `vercel_project` resource for... ** inhales ** - Preview Comments - Git Comments - Auto-assigning custom production domains (or disabling it) - Git LFS - Automatic function failover - Customer Success Code Visibility - Git Fork Protection - Prioritising production builds - Directory listing (wtf even is this) - Skew protection There's a lot of more advanced features among this set, so I think this is a nice one to land. --- client/project.go | 75 +++-- docs/data-sources/project.md | 20 ++ docs/resources/project.md | 19 ++ vercel/data_source_log_drain.go | 2 +- vercel/data_source_project.go | 158 +++++++--- vercel/data_source_project_test.go | 24 ++ vercel/resource_deployment.go | 2 +- vercel/resource_dns_record.go | 16 +- vercel/resource_log_drain.go | 2 +- vercel/resource_project.go | 290 +++++++++++++++--- vercel/resource_project_domain.go | 12 +- .../resource_project_environment_variable.go | 6 +- vercel/resource_project_test.go | 25 ++ vercel/to_team_id.go | 10 + vercel/types_conversions.go | 63 ---- 15 files changed, 534 insertions(+), 190 deletions(-) create mode 100644 vercel/to_team_id.go delete mode 100644 vercel/types_conversions.go diff --git a/client/project.go b/client/project.go index 282d6281..b39b860b 100644 --- a/client/project.go +++ b/client/project.go @@ -40,7 +40,7 @@ type CreateProjectRequest struct { OutputDirectory *string `json:"outputDirectory"` PublicSource *bool `json:"publicSource"` RootDirectory *string `json:"rootDirectory"` - ServerlessFunctionRegion *string `json:"serverlessFunctionRegion,omitempty"` + ServerlessFunctionRegion string `json:"serverlessFunctionRegion,omitempty"` } // CreateProject will create a project within Vercel. @@ -161,16 +161,31 @@ type ProjectResponse struct { ProductionBranch *string `json:"productionBranch"` DeployHooks []DeployHook `json:"deployHooks"` } `json:"link"` - Name string `json:"name"` - OutputDirectory *string `json:"outputDirectory"` - PublicSource *bool `json:"publicSource"` - RootDirectory *string `json:"rootDirectory"` - ServerlessFunctionRegion *string `json:"serverlessFunctionRegion"` - VercelAuthentication *VercelAuthentication `json:"ssoProtection"` - PasswordProtection *PasswordProtection `json:"passwordProtection"` - TrustedIps *TrustedIps `json:"trustedIps"` - ProtectionBypass map[string]ProtectionBypass `json:"protectionBypass"` - AutoExposeSystemEnvVars *bool `json:"autoExposeSystemEnvs"` + Name string `json:"name"` + OutputDirectory *string `json:"outputDirectory"` + PublicSource *bool `json:"publicSource"` + RootDirectory *string `json:"rootDirectory"` + ServerlessFunctionRegion *string `json:"serverlessFunctionRegion"` + VercelAuthentication *VercelAuthentication `json:"ssoProtection"` + PasswordProtection *PasswordProtection `json:"passwordProtection"` + TrustedIps *TrustedIps `json:"trustedIps"` + ProtectionBypass map[string]ProtectionBypass `json:"protectionBypass"` + AutoExposeSystemEnvVars *bool `json:"autoExposeSystemEnvs"` + EnablePreviewFeedback *bool `json:"enablePreviewFeedback"` + AutoAssignCustomDomains bool `json:"autoAssignCustomDomains"` + GitLFS bool `json:"gitLFS"` + ServerlessFunctionZeroConfigFailover bool `json:"serverlessFunctionZeroConfigFailover"` + CustomerSupportCodeVisibility bool `json:"customerSupportCodeVisibility"` + GitForkProtection bool `json:"gitForkProtection"` + ProductionDeploymentsFastLane bool `json:"productionDeploymentsFastLane"` + DirectoryListing bool `json:"directoryListing"` + SkewProtectionMaxAge int `json:"skewProtectionMaxAge"` + GitComments *GitComments `json:"gitComments"` +} + +type GitComments struct { + OnCommit bool `json:"onCommit"` + OnPullRequest bool `json:"onPullRequest"` } // GetProject retrieves information about an existing project from Vercel. @@ -228,20 +243,30 @@ func (c *Client) ListProjects(ctx context.Context, teamID string) (r []ProjectRe // - setting the field to an empty value (e.g. "") will remove the setting for that field. // - omitting the value entirely from the request will _not_ update the field. type UpdateProjectRequest struct { - BuildCommand *string `json:"buildCommand"` - CommandForIgnoringBuildStep *string `json:"commandForIgnoringBuildStep"` - DevCommand *string `json:"devCommand"` - Framework *string `json:"framework"` - InstallCommand *string `json:"installCommand"` - Name *string `json:"name,omitempty"` - OutputDirectory *string `json:"outputDirectory"` - PublicSource *bool `json:"publicSource"` - RootDirectory *string `json:"rootDirectory"` - ServerlessFunctionRegion *string `json:"serverlessFunctionRegion"` - VercelAuthentication *VercelAuthentication `json:"ssoProtection"` - PasswordProtection *PasswordProtectionWithPassword `json:"passwordProtection"` - TrustedIps *TrustedIps `json:"trustedIps"` - AutoExposeSystemEnvVars *bool `json:"autoExposeSystemEnvs,omitempty"` + BuildCommand *string `json:"buildCommand"` + CommandForIgnoringBuildStep *string `json:"commandForIgnoringBuildStep"` + DevCommand *string `json:"devCommand"` + Framework *string `json:"framework"` + InstallCommand *string `json:"installCommand"` + Name *string `json:"name,omitempty"` + OutputDirectory *string `json:"outputDirectory"` + PublicSource *bool `json:"publicSource"` + RootDirectory *string `json:"rootDirectory"` + ServerlessFunctionRegion string `json:"serverlessFunctionRegion,omitempty"` + VercelAuthentication *VercelAuthentication `json:"ssoProtection"` + PasswordProtection *PasswordProtectionWithPassword `json:"passwordProtection"` + TrustedIps *TrustedIps `json:"trustedIps"` + AutoExposeSystemEnvVars bool `json:"autoExposeSystemEnvs"` + EnablePreviewFeedback *bool `json:"enablePreviewFeedback"` + AutoAssignCustomDomains bool `json:"autoAssignCustomDomains"` + GitLFS bool `json:"gitLFS"` + ServerlessFunctionZeroConfigFailover bool `json:"serverlessFunctionZeroConfigFailover"` + CustomerSupportCodeVisibility bool `json:"customerSupportCodeVisibility"` + GitForkProtection bool `json:"gitForkProtection"` + ProductionDeploymentsFastLane bool `json:"productionDeploymentsFastLane"` + DirectoryListing bool `json:"directoryListing"` + SkewProtectionMaxAge int `json:"skewProtectionMaxAge"` + GitComments *GitComments `json:"gitComments"` } // UpdateProject updates an existing projects configuration within Vercel. diff --git a/docs/data-sources/project.md b/docs/data-sources/project.md index a1697e66..33b78e32 100644 --- a/docs/data-sources/project.md +++ b/docs/data-sources/project.md @@ -42,20 +42,31 @@ output "project_id" { ### Read-Only +- `auto_assign_custom_domains` (Boolean) Automatically assign custom production domains after each Production deployment via merge to the production branch or Vercel CLI deploy with --prod. Defaults to `true` - `automatically_expose_system_environment_variables` (Boolean) Vercel provides a set of Environment Variables that are automatically populated by the System, such as the URL of the Deployment or the name of the Git branch deployed. To expose them to your Deployments, enable this field - `build_command` (String) The build command for this project. If omitted, this value will be automatically detected. +- `customer_success_code_visibility` (Boolean) Allows Vercel Customer Support to inspect all Deployments' source code in this project to assist with debugging. - `dev_command` (String) The dev command for this project. If omitted, this value will be automatically detected. +- `directory_listing` (Boolean) If no index file is present within a directory, the directory contents will be displayed. - `environment` (Attributes Set) A list of environment variables that should be configured for the project. (see [below for nested schema](#nestedatt--environment)) - `framework` (String) The framework that is being used for this project. If omitted, no framework is selected. +- `function_failover` (Boolean) Automatically failover Serverless Functions to the nearest region. You can customize regions through vercel.json. A new Deployment is required for your changes to take effect. +- `git_comments` (Attributes) Configuration for Git Comments. (see [below for nested schema](#nestedatt--git_comments)) +- `git_fork_protection` (Boolean) Ensures that pull requests targeting your Git repository must be authorized by a member of your Team before deploying if your Project has Environment Variables or if the pull request includes a change to vercel.json. +- `git_lfs` (Boolean) Enables Git LFS support. Git LFS replaces large files such as audio samples, videos, datasets, and graphics with text pointers inside Git, while storing the file contents on a remote server like GitHub.com or GitHub Enterprise. - `git_repository` (Attributes) The Git Repository that will be connected to the project. When this is defined, any pushes to the specified connected Git Repository will be automatically deployed. This requires the corresponding Vercel for [Github](https://vercel.com/docs/concepts/git/vercel-for-github), [Gitlab](https://vercel.com/docs/concepts/git/vercel-for-gitlab) or [Bitbucket](https://vercel.com/docs/concepts/git/vercel-for-bitbucket) plugins to be installed. (see [below for nested schema](#nestedatt--git_repository)) - `id` (String) The ID of this resource. - `ignore_command` (String) When a commit is pushed to the Git repository that is connected with your Project, its SHA will determine if a new Build has to be issued. If the SHA was deployed before, no new Build will be issued. You can customize this behavior with a command that exits with code 1 (new Build needed) or code 0. - `install_command` (String) The install command for this project. If omitted, this value will be automatically detected. - `output_directory` (String) The output directory of the project. When null is used 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)) +- `preview_comments` (Boolean) Whether comments are enabled on your Preview Deployments. +- `prioritise_production_builds` (Boolean) If enabled, builds for the Production environment will be prioritized over Preview environments. +- `protection_bypass_for_automation` (Boolean) Allows automation services to bypass Vercel Authentication and Password Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass`. - `public_source` (Boolean) Specifies whether the source code and logs of the deployments for this project should be public or not. - `root_directory` (String) The name of a directory or relative path to the source code of your project. When null is used it will default to the project root. - `serverless_function_region` (String) The region on Vercel's network to which your Serverless Functions are deployed. It should be close to any data source your Serverless Function might depend on. A new Deployment is required for your changes to take effect. Please see [Vercel's documentation](https://vercel.com/docs/concepts/edge-network/regions) for a full list of regions. +- `skew_protection` (String) Ensures that outdated clients always fetch the correct version for a given deployment. This value defines how long Vercel keeps Skew Protection active. - `trusted_ips` (Attributes) Ensures only visitors from an allowed IP address can access your deployment. (see [below for nested schema](#nestedatt--trusted_ips)) - `vercel_authentication` (Attributes) Ensures visitors to your Preview Deployments are logged into Vercel and have a minimum of Viewer access on your team. (see [below for nested schema](#nestedatt--vercel_authentication)) @@ -72,6 +83,15 @@ Read-Only: - `value` (String) The value of the environment variable. + +### Nested Schema for `git_comments` + +Required: + +- `on_commit` (Boolean) Whether Commit comments are enabled +- `on_pull_request` (Boolean) Whether Pull Request comments are enabled + + ### Nested Schema for `git_repository` diff --git a/docs/resources/project.md b/docs/resources/project.md index 05839611..d300595f 100644 --- a/docs/resources/project.md +++ b/docs/resources/project.md @@ -55,20 +55,30 @@ resource "vercel_project" "example" { ### Optional +- `auto_assign_custom_domains` (Boolean) Automatically assign custom production domains after each Production deployment via merge to the production branch or Vercel CLI deploy with --prod. Defaults to `true` - `automatically_expose_system_environment_variables` (Boolean) Vercel provides a set of Environment Variables that are automatically populated by the System, such as the URL of the Deployment or the name of the Git branch deployed. To expose them to your Deployments, enable this field - `build_command` (String) The build command for this project. If omitted, this value will be automatically detected. +- `customer_success_code_visibility` (Boolean) Allows Vercel Customer Support to inspect all Deployments' source code in this project to assist with debugging. - `dev_command` (String) The dev command for this project. If omitted, this value will be automatically detected. +- `directory_listing` (Boolean) If no index file is present within a directory, the directory contents will be displayed. - `environment` (Attributes Set) A set of Environment Variables that should be configured for the project. (see [below for nested schema](#nestedatt--environment)) - `framework` (String) The framework that is being used for this project. If omitted, no framework is selected. +- `function_failover` (Boolean) Automatically failover Serverless Functions to the nearest region. You can customize regions through vercel.json. A new Deployment is required for your changes to take effect. +- `git_comments` (Attributes) Configuration for Git Comments. (see [below for nested schema](#nestedatt--git_comments)) +- `git_fork_protection` (Boolean) Ensures that pull requests targeting your Git repository must be authorized by a member of your Team before deploying if your Project has Environment Variables or if the pull request includes a change to vercel.json. Defaults to `true`. +- `git_lfs` (Boolean) Enables Git LFS support. Git LFS replaces large files such as audio samples, videos, datasets, and graphics with text pointers inside Git, while storing the file contents on a remote server like GitHub.com or GitHub Enterprise. - `git_repository` (Attributes) The Git Repository that will be connected to the project. When this is defined, any pushes to the specified connected Git Repository will be automatically deployed. This requires the corresponding Vercel for [Github](https://vercel.com/docs/concepts/git/vercel-for-github), [Gitlab](https://vercel.com/docs/concepts/git/vercel-for-gitlab) or [Bitbucket](https://vercel.com/docs/concepts/git/vercel-for-bitbucket) plugins to be installed. (see [below for nested schema](#nestedatt--git_repository)) - `ignore_command` (String) When a commit is pushed to the Git repository that is connected with your Project, its SHA will determine if a new Build has to be issued. If the SHA was deployed before, no new Build will be issued. You can customize this behavior with a command that exits with code 1 (new Build needed) or code 0. - `install_command` (String) The install command for this project. If omitted, this value will be automatically detected. - `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)) +- `preview_comments` (Boolean) Whether to enable comments on your Preview Deployments. If omitted, comments are controlled at the team level (default behaviour). +- `prioritise_production_builds` (Boolean) If enabled, builds for the Production environment will be prioritized over Preview environments. - `protection_bypass_for_automation` (Boolean) Allow automation services to bypass Vercel Authentication and Password Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass` with a value of the `password_protection_for_automation_secret` field. - `public_source` (Boolean) By default, visitors to the `/_logs` and `/_src` paths of your Production and Preview Deployments must log in with Vercel (requires being a member of your team) to see the Source, Logs and Deployment Status of your project. Setting `public_source` to `true` disables this behaviour, meaning the Source, Logs and Deployment Status can be publicly viewed. - `root_directory` (String) The name of a directory or relative path to the source code of your project. If omitted, it will default to the project root. - `serverless_function_region` (String) The region on Vercel's network to which your Serverless Functions are deployed. It should be close to any data source your Serverless Function might depend on. A new Deployment is required for your changes to take effect. Please see [Vercel's documentation](https://vercel.com/docs/concepts/edge-network/regions) for a full list of regions. +- `skew_protection` (String) Ensures that outdated clients always fetch the correct version for a given deployment. This value defines how long Vercel keeps Skew Protection active. - `team_id` (String) The team ID to add the project to. Required when configuring a team resource if a default team has not been set in the provider. - `trusted_ips` (Attributes) Ensures only visitors from an allowed IP address can access your deployment. (see [below for nested schema](#nestedatt--trusted_ips)) - `vercel_authentication` (Attributes) Ensures visitors to your Preview Deployments are logged into Vercel and have a minimum of Viewer access on your team. (see [below for nested schema](#nestedatt--vercel_authentication)) @@ -97,6 +107,15 @@ Read-Only: - `id` (String) The ID of the Environment Variable. + +### Nested Schema for `git_comments` + +Required: + +- `on_commit` (Boolean) Whether Commit comments are enabled +- `on_pull_request` (Boolean) Whether Pull Request comments are enabled + + ### Nested Schema for `git_repository` diff --git a/vercel/data_source_log_drain.go b/vercel/data_source_log_drain.go index bd6251dc..becb58ff 100644 --- a/vercel/data_source_log_drain.go +++ b/vercel/data_source_log_drain.go @@ -141,7 +141,7 @@ func responseToLogDrainWithoutSecret(ctx context.Context, out client.LogDrain) ( ID: types.StringValue(out.ID), TeamID: toTeamID(out.TeamID), DeliveryFormat: types.StringValue(out.DeliveryFormat), - SamplingRate: fromFloat64Pointer(out.SamplingRate), + SamplingRate: types.Float64PointerValue(out.SamplingRate), Endpoint: types.StringValue(out.Endpoint), Environments: environments, Headers: headers, diff --git a/vercel/data_source_project.go b/vercel/data_source_project.go index 0324609a..36126643 100644 --- a/vercel/data_source_project.go +++ b/vercel/data_source_project.go @@ -16,7 +16,8 @@ import ( // Ensure the implementation satisfies the expected interfaces. var ( - _ datasource.DataSource = &projectDataSource{} + _ datasource.DataSource = &projectDataSource{} + _ datasource.DataSourceWithConfigure = &projectDataSource{} ) func newProjectDataSource() datasource.DataSource { @@ -213,38 +214,110 @@ For more detailed information, please see the [Vercel documentation](https://ver Computed: true, Description: "The name of a directory or relative path to the source code of your project. When null is used it will default to the project root.", }, + "protection_bypass_for_automation": schema.BoolAttribute{ + Computed: true, + Description: "Allows automation services to bypass Vercel Authentication and Password Protection for both Preview and Production Deployments on this project when using an HTTP header named `x-vercel-protection-bypass`.", + }, "automatically_expose_system_environment_variables": schema.BoolAttribute{ Computed: true, Description: "Vercel provides a set of Environment Variables that are automatically populated by the System, such as the URL of the Deployment or the name of the Git branch deployed. To expose them to your Deployments, enable this field", }, + "git_comments": schema.SingleNestedAttribute{ + Description: "Configuration for Git Comments.", + Computed: true, + Attributes: map[string]schema.Attribute{ + "on_pull_request": schema.BoolAttribute{ + Description: "Whether Pull Request comments are enabled", + Required: true, + }, + "on_commit": schema.BoolAttribute{ + Description: "Whether Commit comments are enabled", + Required: true, + }, + }, + }, + "preview_comments": schema.BoolAttribute{ + Computed: true, + Description: "Whether comments are enabled on your Preview Deployments.", + }, + "auto_assign_custom_domains": schema.BoolAttribute{ + Computed: true, + Description: "Automatically assign custom production domains after each Production deployment via merge to the production branch or Vercel CLI deploy with --prod. Defaults to `true`", + }, + "git_lfs": schema.BoolAttribute{ + Computed: true, + Description: "Enables Git LFS support. Git LFS replaces large files such as audio samples, videos, datasets, and graphics with text pointers inside Git, while storing the file contents on a remote server like GitHub.com or GitHub Enterprise.", + }, + "function_failover": schema.BoolAttribute{ + Computed: true, + Description: "Automatically failover Serverless Functions to the nearest region. You can customize regions through vercel.json. A new Deployment is required for your changes to take effect.", + }, + "customer_success_code_visibility": schema.BoolAttribute{ + Computed: true, + Description: "Allows Vercel Customer Support to inspect all Deployments' source code in this project to assist with debugging.", + }, + "git_fork_protection": schema.BoolAttribute{ + Computed: true, + Description: "Ensures that pull requests targeting your Git repository must be authorized by a member of your Team before deploying if your Project has Environment Variables or if the pull request includes a change to vercel.json.", + }, + "prioritise_production_builds": schema.BoolAttribute{ + Computed: true, + Description: "If enabled, builds for the Production environment will be prioritized over Preview environments.", + }, + "directory_listing": schema.BoolAttribute{ + Computed: true, + Description: "If no index file is present within a directory, the directory contents will be displayed.", + }, + "skew_protection": schema.StringAttribute{ + Computed: true, + Description: "Ensures that outdated clients always fetch the correct version for a given deployment. This value defines how long Vercel keeps Skew Protection active.", + }, }, } } // Project reflects the state terraform stores internally for a project. type ProjectDataSource struct { - BuildCommand types.String `tfsdk:"build_command"` - DevCommand types.String `tfsdk:"dev_command"` - Environment types.Set `tfsdk:"environment"` - Framework types.String `tfsdk:"framework"` - GitRepository *GitRepository `tfsdk:"git_repository"` - ID types.String `tfsdk:"id"` - IgnoreCommand types.String `tfsdk:"ignore_command"` - InstallCommand types.String `tfsdk:"install_command"` - Name types.String `tfsdk:"name"` - OutputDirectory types.String `tfsdk:"output_directory"` - PublicSource types.Bool `tfsdk:"public_source"` - RootDirectory types.String `tfsdk:"root_directory"` - ServerlessFunctionRegion types.String `tfsdk:"serverless_function_region"` - TeamID types.String `tfsdk:"team_id"` - VercelAuthentication *VercelAuthentication `tfsdk:"vercel_authentication"` - PasswordProtection *PasswordProtection `tfsdk:"password_protection"` - TrustedIps *TrustedIps `tfsdk:"trusted_ips"` - AutoExposeSystemEnvVars types.Bool `tfsdk:"automatically_expose_system_environment_variables"` + BuildCommand types.String `tfsdk:"build_command"` + DevCommand types.String `tfsdk:"dev_command"` + Environment types.Set `tfsdk:"environment"` + Framework types.String `tfsdk:"framework"` + GitRepository *GitRepository `tfsdk:"git_repository"` + ID types.String `tfsdk:"id"` + IgnoreCommand types.String `tfsdk:"ignore_command"` + InstallCommand types.String `tfsdk:"install_command"` + Name types.String `tfsdk:"name"` + OutputDirectory types.String `tfsdk:"output_directory"` + PublicSource types.Bool `tfsdk:"public_source"` + RootDirectory types.String `tfsdk:"root_directory"` + ServerlessFunctionRegion types.String `tfsdk:"serverless_function_region"` + TeamID types.String `tfsdk:"team_id"` + VercelAuthentication *VercelAuthentication `tfsdk:"vercel_authentication"` + PasswordProtection *PasswordProtection `tfsdk:"password_protection"` + TrustedIps *TrustedIps `tfsdk:"trusted_ips"` + ProtectionBypassForAutomation types.Bool `tfsdk:"protection_bypass_for_automation"` + AutoExposeSystemEnvVars types.Bool `tfsdk:"automatically_expose_system_environment_variables"` + GitComments types.Object `tfsdk:"git_comments"` + PreviewComments types.Bool `tfsdk:"preview_comments"` + AutoAssignCustomDomains types.Bool `tfsdk:"auto_assign_custom_domains"` + GitLFS types.Bool `tfsdk:"git_lfs"` + FunctionFailover types.Bool `tfsdk:"function_failover"` + CustomerSuccessCodeVisibility types.Bool `tfsdk:"customer_success_code_visibility"` + GitForkProtection types.Bool `tfsdk:"git_fork_protection"` + PrioritiseProductionBuilds types.Bool `tfsdk:"prioritise_production_builds"` + DirectoryListing types.Bool `tfsdk:"directory_listing"` + SkewProtection types.String `tfsdk:"skew_protection"` } func convertResponseToProjectDataSource(ctx context.Context, response client.ProjectResponse, plan Project, environmentVariables []client.EnvironmentVariable) (ProjectDataSource, error) { + /* Force reading of environment and git comments. These are ignored usually if the planned value is null, + otherwise it causes issues with terraform thinking there are changes when there aren't. However, + for the data source we always want to read the value */ plan.Environment = types.SetValueMust(envVariableElemType, []attr.Value{}) + plan.GitComments = types.ObjectValueMust(gitCommentsAttrTypes, map[string]attr.Value{ + "on_pull_request": types.BoolValue(response.GitComments.OnPullRequest), + "on_commit": types.BoolValue(response.GitComments.OnCommit), + }) project, err := convertResponseToProject(ctx, response, plan, environmentVariables) if err != nil { return ProjectDataSource{}, err @@ -257,24 +330,35 @@ func convertResponseToProjectDataSource(ctx context.Context, response client.Pro } } return ProjectDataSource{ - BuildCommand: project.BuildCommand, - DevCommand: project.DevCommand, - Environment: project.Environment, - Framework: project.Framework, - GitRepository: project.GitRepository, - ID: project.ID, - IgnoreCommand: project.IgnoreCommand, - InstallCommand: project.InstallCommand, - Name: project.Name, - OutputDirectory: project.OutputDirectory, - PublicSource: project.PublicSource, - RootDirectory: project.RootDirectory, - ServerlessFunctionRegion: project.ServerlessFunctionRegion, - TeamID: project.TeamID, - VercelAuthentication: project.VercelAuthentication, - PasswordProtection: pp, - TrustedIps: project.TrustedIps, - AutoExposeSystemEnvVars: fromBoolPointer(response.AutoExposeSystemEnvVars), + BuildCommand: project.BuildCommand, + DevCommand: project.DevCommand, + Environment: project.Environment, + Framework: project.Framework, + GitRepository: project.GitRepository, + ID: project.ID, + IgnoreCommand: project.IgnoreCommand, + InstallCommand: project.InstallCommand, + Name: project.Name, + OutputDirectory: project.OutputDirectory, + PublicSource: project.PublicSource, + RootDirectory: project.RootDirectory, + ServerlessFunctionRegion: project.ServerlessFunctionRegion, + TeamID: project.TeamID, + VercelAuthentication: project.VercelAuthentication, + PasswordProtection: pp, + TrustedIps: project.TrustedIps, + AutoExposeSystemEnvVars: types.BoolPointerValue(response.AutoExposeSystemEnvVars), + ProtectionBypassForAutomation: project.ProtectionBypassForAutomation, + GitComments: project.GitComments, + PreviewComments: project.PreviewComments, + AutoAssignCustomDomains: project.AutoAssignCustomDomains, + GitLFS: project.GitLFS, + FunctionFailover: project.FunctionFailover, + CustomerSuccessCodeVisibility: project.CustomerSuccessCodeVisibility, + GitForkProtection: project.GitForkProtection, + PrioritiseProductionBuilds: project.PrioritiseProductionBuilds, + DirectoryListing: project.DirectoryListing, + SkewProtection: project.SkewProtection, }, nil } diff --git a/vercel/data_source_project_test.go b/vercel/data_source_project_test.go index a2adc904..8f1e23eb 100644 --- a/vercel/data_source_project_test.go +++ b/vercel/data_source_project_test.go @@ -41,6 +41,17 @@ func TestAcc_ProjectDataSource(t *testing.T) { "value": "bar", }), resource.TestCheckTypeSetElemAttr("data.vercel_project.test", "environment.0.target.*", "production"), + resource.TestCheckResourceAttr("data.vercel_project.test", "git_comments.on_pull_request", "true"), + resource.TestCheckResourceAttr("data.vercel_project.test", "git_comments.on_commit", "true"), + resource.TestCheckResourceAttr("data.vercel_project.test", "preview_comments", "true"), + resource.TestCheckResourceAttr("data.vercel_project.test", "auto_assign_custom_domains", "true"), + resource.TestCheckResourceAttr("data.vercel_project.test", "git_lfs", "true"), + resource.TestCheckResourceAttr("data.vercel_project.test", "function_failover", "true"), + resource.TestCheckResourceAttr("data.vercel_project.test", "customer_success_code_visibility", "true"), + resource.TestCheckResourceAttr("data.vercel_project.test", "git_fork_protection", "true"), + resource.TestCheckResourceAttr("data.vercel_project.test", "prioritise_production_builds", "true"), + resource.TestCheckResourceAttr("data.vercel_project.test", "directory_listing", "true"), + resource.TestCheckResourceAttr("data.vercel_project.test", "skew_protection", "7 days"), ), }, }, @@ -84,6 +95,19 @@ resource "vercel_project" "test" { } ] automatically_expose_system_environment_variables = true + git_comments = { + on_pull_request = true, + on_commit = true + } + preview_comments = true + auto_assign_custom_domains = true + git_lfs = true + function_failover = true + customer_success_code_visibility = true + git_fork_protection = true + prioritise_production_builds = true + directory_listing = true + skew_protection = "7 days" } data "vercel_project" "test" { diff --git a/vercel/resource_deployment.go b/vercel/resource_deployment.go index 3483dfad..c6ceabfd 100644 --- a/vercel/resource_deployment.go +++ b/vercel/resource_deployment.go @@ -212,7 +212,7 @@ func setIfNotUnknown(m map[string]interface{}, v types.String, name string) { m[name] = nil } if v.ValueString() != "" { - m[name] = toPtr(v.ValueString()) + m[name] = v.ValueStringPointer() } } diff --git a/vercel/resource_dns_record.go b/vercel/resource_dns_record.go index ab0761fe..ffc29127 100644 --- a/vercel/resource_dns_record.go +++ b/vercel/resource_dns_record.go @@ -208,18 +208,18 @@ func (d DNSRecord) toUpdateRequest() client.UpdateDNSRecordRequest { var srv *client.SRVUpdate = nil if d.SRV != nil { srv = &client.SRVUpdate{ - Port: toPtr(d.SRV.Port.ValueInt64()), - Priority: toPtr(d.SRV.Priority.ValueInt64()), - Target: toPtr(d.SRV.Target.ValueString()), - Weight: toPtr(d.SRV.Weight.ValueInt64()), + Port: d.SRV.Port.ValueInt64Pointer(), + Priority: d.SRV.Priority.ValueInt64Pointer(), + Target: d.SRV.Target.ValueStringPointer(), + Weight: d.SRV.Weight.ValueInt64Pointer(), } } return client.UpdateDNSRecordRequest{ - MXPriority: toInt64Pointer(d.MXPriority), - Name: toPtr(d.Name.ValueString()), + MXPriority: d.MXPriority.ValueInt64Pointer(), + Name: d.Name.ValueStringPointer(), SRV: srv, - TTL: toInt64Pointer(d.TTL), - Value: toStrPointer(d.Value), + TTL: d.TTL.ValueInt64Pointer(), + Value: d.Value.ValueStringPointer(), Comment: d.Comment.ValueString(), } } diff --git a/vercel/resource_log_drain.go b/vercel/resource_log_drain.go index 35d61a35..931e6c9c 100644 --- a/vercel/resource_log_drain.go +++ b/vercel/resource_log_drain.go @@ -192,7 +192,7 @@ func responseToLogDrain(ctx context.Context, out client.LogDrain, secret types.S ID: types.StringValue(out.ID), TeamID: toTeamID(out.TeamID), DeliveryFormat: types.StringValue(out.DeliveryFormat), - SamplingRate: fromFloat64Pointer(out.SamplingRate), + SamplingRate: types.Float64PointerValue(out.SamplingRate), Secret: secret, Endpoint: types.StringValue(out.Endpoint), Environments: environments, diff --git a/vercel/resource_project.go b/vercel/resource_project.go index 33197d16..570260f5 100644 --- a/vercel/resource_project.go +++ b/vercel/resource_project.go @@ -9,6 +9,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" @@ -18,6 +19,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/vercel/terraform-provider-vercel/client" ) @@ -104,8 +106,8 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ "serverless_function_region": schema.StringAttribute{ Optional: true, Computed: true, - Description: "The region on Vercel's network to which your Serverless Functions are deployed. It should be close to any data source your Serverless Function might depend on. A new Deployment is required for your changes to take effect. Please see [Vercel's documentation](https://vercel.com/docs/concepts/edge-network/regions) for a full list of regions.", PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, + Description: "The region on Vercel's network to which your Serverless Functions are deployed. It should be close to any data source your Serverless Function might depend on. A new Deployment is required for your changes to take effect. Please see [Vercel's documentation](https://vercel.com/docs/concepts/edge-network/regions) for a full list of regions.", Validators: []validator.String{ validateServerlessFunctionRegion(), }, @@ -173,9 +175,10 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ Required: true, }, "production_branch": schema.StringAttribute{ - Description: "By default, every commit pushed to the main branch will trigger a Production Deployment instead of the usual Preview Deployment. You can switch to a different branch here.", - Optional: true, - Computed: true, + Description: "By default, every commit pushed to the main branch will trigger a Production Deployment instead of the usual Preview Deployment. You can switch to a different branch here.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "deploy_hooks": schema.SetNestedAttribute{ Description: "Deploy hooks are unique URLs that allow you to trigger a deployment of a given branch. See https://vercel.com/docs/deployments/deploy-hooks for full information.", @@ -326,8 +329,76 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ "automatically_expose_system_environment_variables": schema.BoolAttribute{ Optional: true, Computed: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, Description: "Vercel provides a set of Environment Variables that are automatically populated by the System, such as the URL of the Deployment or the name of the Git branch deployed. To expose them to your Deployments, enable this field", + }, + "git_comments": schema.SingleNestedAttribute{ + Description: "Configuration for Git Comments.", + Optional: true, + Attributes: map[string]schema.Attribute{ + "on_pull_request": schema.BoolAttribute{ + Description: "Whether Pull Request comments are enabled", + Required: true, + }, + "on_commit": schema.BoolAttribute{ + Description: "Whether Commit comments are enabled", + Required: true, + }, + }, + }, + "preview_comments": schema.BoolAttribute{ + Optional: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, + Description: "Whether to enable comments on your Preview Deployments. If omitted, comments are controlled at the team level (default behaviour).", + }, + "auto_assign_custom_domains": schema.BoolAttribute{ + Optional: true, + Computed: true, + Description: "Automatically assign custom production domains after each Production deployment via merge to the production branch or Vercel CLI deploy with --prod. Defaults to `true`", + Default: booldefault.StaticBool(true), + }, + "git_lfs": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, + Description: "Enables Git LFS support. Git LFS replaces large files such as audio samples, videos, datasets, and graphics with text pointers inside Git, while storing the file contents on a remote server like GitHub.com or GitHub Enterprise.", + }, + "function_failover": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, + Description: "Automatically failover Serverless Functions to the nearest region. You can customize regions through vercel.json. A new Deployment is required for your changes to take effect.", + }, + "customer_success_code_visibility": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, + Description: "Allows Vercel Customer Support to inspect all Deployments' source code in this project to assist with debugging.", + }, + "git_fork_protection": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + Description: "Ensures that pull requests targeting your Git repository must be authorized by a member of your Team before deploying if your Project has Environment Variables or if the pull request includes a change to vercel.json. Defaults to `true`.", + }, + "prioritise_production_builds": schema.BoolAttribute{ + Optional: true, + Computed: true, PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, + Description: "If enabled, builds for the Production environment will be prioritized over Preview environments.", + }, + "directory_listing": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{boolplanmodifier.UseStateForUnknown()}, + Description: "If no index file is present within a directory, the directory contents will be displayed.", + }, + "skew_protection": schema.StringAttribute{ + Optional: true, + Description: "Ensures that outdated clients always fetch the correct version for a given deployment. This value defines how long Vercel keeps Skew Protection active.", + Validators: []validator.String{ + stringOneOf("30 minutes", "12 hours", "1 day", "7 days"), + }, }, }, } @@ -355,6 +426,48 @@ type Project struct { ProtectionBypassForAutomation types.Bool `tfsdk:"protection_bypass_for_automation"` ProtectionBypassForAutomationSecret types.String `tfsdk:"protection_bypass_for_automation_secret"` AutoExposeSystemEnvVars types.Bool `tfsdk:"automatically_expose_system_environment_variables"` + GitComments types.Object `tfsdk:"git_comments"` + PreviewComments types.Bool `tfsdk:"preview_comments"` + AutoAssignCustomDomains types.Bool `tfsdk:"auto_assign_custom_domains"` + GitLFS types.Bool `tfsdk:"git_lfs"` + FunctionFailover types.Bool `tfsdk:"function_failover"` + CustomerSuccessCodeVisibility types.Bool `tfsdk:"customer_success_code_visibility"` + GitForkProtection types.Bool `tfsdk:"git_fork_protection"` + PrioritiseProductionBuilds types.Bool `tfsdk:"prioritise_production_builds"` + DirectoryListing types.Bool `tfsdk:"directory_listing"` + SkewProtection types.String `tfsdk:"skew_protection"` +} + +type GitComments struct { + OnPullRequest types.Bool `tfsdk:"on_pull_request"` + OnCommit types.Bool `tfsdk:"on_commit"` +} + +func (g *GitComments) toUpdateProjectRequest() *client.GitComments { + if g == nil { + return nil + } + return &client.GitComments{ + OnPullRequest: g.OnPullRequest.ValueBool(), + OnCommit: g.OnCommit.ValueBool(), + } +} + +func (p Project) RequiresUpdateAfterCreation() bool { + return p.PasswordProtection != nil || + p.VercelAuthentication != nil || + p.TrustedIps != nil || + !p.AutoExposeSystemEnvVars.IsNull() || + p.GitComments.IsNull() || + !p.PreviewComments.IsNull() || + (!p.AutoAssignCustomDomains.IsNull() && !p.AutoAssignCustomDomains.ValueBool()) || + !p.GitLFS.IsNull() || + !p.FunctionFailover.IsNull() || + !p.CustomerSuccessCodeVisibility.IsNull() || + (!p.GitForkProtection.IsNull() && !p.GitForkProtection.ValueBool()) || + !p.PrioritiseProductionBuilds.IsNull() || + !p.DirectoryListing.IsNull() || + !p.SkewProtection.IsNull() } var nullProject = Project{ @@ -400,7 +513,7 @@ func parseEnvironment(vars []EnvironmentItem) []client.EnvironmentVariable { Key: e.Key.ValueString(), Value: e.Value.ValueString(), Target: target, - GitBranch: toStrPointer(e.GitBranch), + GitBranch: e.GitBranch.ValueStringPointer(), Type: envVariableType, ID: e.ID.ValueString(), }) @@ -410,43 +523,74 @@ func parseEnvironment(vars []EnvironmentItem) []client.EnvironmentVariable { func (p *Project) toCreateProjectRequest(envs []EnvironmentItem) client.CreateProjectRequest { return client.CreateProjectRequest{ - BuildCommand: toStrPointer(p.BuildCommand), - CommandForIgnoringBuildStep: toStrPointer(p.IgnoreCommand), - DevCommand: toStrPointer(p.DevCommand), + BuildCommand: p.BuildCommand.ValueStringPointer(), + CommandForIgnoringBuildStep: p.IgnoreCommand.ValueStringPointer(), + DevCommand: p.DevCommand.ValueStringPointer(), EnvironmentVariables: parseEnvironment(envs), - Framework: toStrPointer(p.Framework), + Framework: p.Framework.ValueStringPointer(), GitRepository: p.GitRepository.toCreateProjectRequest(), - InstallCommand: toStrPointer(p.InstallCommand), + InstallCommand: p.InstallCommand.ValueStringPointer(), Name: p.Name.ValueString(), - OutputDirectory: toStrPointer(p.OutputDirectory), - PublicSource: toBoolPointer(p.PublicSource), - RootDirectory: toStrPointer(p.RootDirectory), - ServerlessFunctionRegion: toStrPointer(p.ServerlessFunctionRegion), + OutputDirectory: p.OutputDirectory.ValueStringPointer(), + PublicSource: p.PublicSource.ValueBoolPointer(), + RootDirectory: p.RootDirectory.ValueStringPointer(), + ServerlessFunctionRegion: p.ServerlessFunctionRegion.ValueString(), + } +} + +func toSkewProtectionAge(sp types.String) int { + if sp.IsNull() || sp.IsUnknown() { + return 0 + } + var ages = map[string]int{ + "30 minutes": 1800, + "12 hours": 43200, + "1 day": 86400, + "7 days": 604800, } + return ages[sp.ValueString()] } -func (p *Project) toUpdateProjectRequest(oldName string) client.UpdateProjectRequest { +func (p *Project) toUpdateProjectRequest(ctx context.Context, oldName string) (req client.UpdateProjectRequest, diags diag.Diagnostics) { var name *string = nil if oldName != p.Name.ValueString() { n := p.Name.ValueString() name = &n } - return client.UpdateProjectRequest{ - BuildCommand: toStrPointer(p.BuildCommand), - CommandForIgnoringBuildStep: toStrPointer(p.IgnoreCommand), - DevCommand: toStrPointer(p.DevCommand), - Framework: toStrPointer(p.Framework), - InstallCommand: toStrPointer(p.InstallCommand), - Name: name, - OutputDirectory: toStrPointer(p.OutputDirectory), - PublicSource: toBoolPointer(p.PublicSource), - RootDirectory: toStrPointer(p.RootDirectory), - ServerlessFunctionRegion: toStrPointer(p.ServerlessFunctionRegion), - PasswordProtection: p.PasswordProtection.toUpdateProjectRequest(), - VercelAuthentication: p.VercelAuthentication.toUpdateProjectRequest(), - TrustedIps: p.TrustedIps.toUpdateProjectRequest(), - AutoExposeSystemEnvVars: toBoolPointer(p.AutoExposeSystemEnvVars), + var gc *GitComments + diags = p.GitComments.As(ctx, &gc, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }) + if diags.HasError() { + return req, diags } + return client.UpdateProjectRequest{ + BuildCommand: p.BuildCommand.ValueStringPointer(), + CommandForIgnoringBuildStep: p.IgnoreCommand.ValueStringPointer(), + DevCommand: p.DevCommand.ValueStringPointer(), + Framework: p.Framework.ValueStringPointer(), + InstallCommand: p.InstallCommand.ValueStringPointer(), + Name: name, + OutputDirectory: p.OutputDirectory.ValueStringPointer(), + PublicSource: p.PublicSource.ValueBoolPointer(), + RootDirectory: p.RootDirectory.ValueStringPointer(), + ServerlessFunctionRegion: p.ServerlessFunctionRegion.ValueString(), + PasswordProtection: p.PasswordProtection.toUpdateProjectRequest(), + VercelAuthentication: p.VercelAuthentication.toUpdateProjectRequest(), + TrustedIps: p.TrustedIps.toUpdateProjectRequest(), + AutoExposeSystemEnvVars: p.AutoExposeSystemEnvVars.ValueBool(), + EnablePreviewFeedback: p.PreviewComments.ValueBoolPointer(), + AutoAssignCustomDomains: p.AutoAssignCustomDomains.ValueBool(), + GitLFS: p.GitLFS.ValueBool(), + ServerlessFunctionZeroConfigFailover: p.FunctionFailover.ValueBool(), + CustomerSupportCodeVisibility: p.CustomerSuccessCodeVisibility.ValueBool(), + GitForkProtection: p.GitForkProtection.ValueBool(), + ProductionDeploymentsFastLane: p.PrioritiseProductionBuilds.ValueBool(), + DirectoryListing: p.DirectoryListing.ValueBool(), + SkewProtectionMaxAge: toSkewProtectionAge(p.SkewProtection), + GitComments: gc.toUpdateProjectRequest(), + }, nil } // EnvironmentItem reflects the state terraform stores internally for a project's environment variable. @@ -477,7 +621,7 @@ func (e *EnvironmentItem) toEnvironmentVariableRequest() client.EnvironmentVaria Key: e.Key.ValueString(), Value: e.Value.ValueString(), Target: target, - GitBranch: toStrPointer(e.GitBranch), + GitBranch: e.GitBranch.ValueStringPointer(), Type: envVariableType, } } @@ -671,6 +815,11 @@ var envVariableElemType = types.ObjectType{ }, } +var gitCommentsAttrTypes = map[string]attr.Type{ + "on_commit": types.BoolType, + "on_pull_request": types.BoolType, +} + func hasSameTarget(p EnvironmentItem, target []string) bool { if len(p.Target) != len(target) { return false @@ -700,6 +849,23 @@ type deployHook struct { ID string `tfsdk:"id"` } +func fromSkewProtectionMaxAge(sp int) types.String { + if sp == 0 { + return types.StringNull() + } + var ages = map[int]string{ + 1800: "30 minutes", + 43200: "12 hours", + 86400: "1 day", + 604800: "7 days", + } + v, ok := ages[sp] + if !ok { + return types.StringValue(fmt.Sprintf("unknown - %d seconds", sp)) + } + return types.StringValue(v) +} + func convertResponseToProject(ctx context.Context, response client.ProjectResponse, plan Project, environmentVariables []client.EnvironmentVariable) (Project, error) { fields := plan.coercedFields() @@ -805,7 +971,7 @@ func convertResponseToProject(ctx context.Context, response client.ProjectRespon "key": types.StringValue(e.Key), "value": value, "target": types.SetValueMust(types.StringType, target), - "git_branch": fromStringPointer(e.GitBranch), + "git_branch": types.StringPointerValue(e.GitBranch), "id": types.StringValue(e.ID), "sensitive": types.BoolValue(e.Type == "sensitive"), }, @@ -830,27 +996,49 @@ func convertResponseToProject(ctx context.Context, response client.ProjectRespon environmentEntry = types.SetNull(envVariableElemType) } + gitComments := types.ObjectNull(gitCommentsAttrTypes) + if response.GitComments != nil && !plan.GitComments.IsNull() { + var diags diag.Diagnostics + gitComments, diags = types.ObjectValueFrom(ctx, gitCommentsAttrTypes, &GitComments{ + OnPullRequest: types.BoolValue(response.GitComments.OnPullRequest), + OnCommit: types.BoolValue(response.GitComments.OnCommit), + }) + if diags.HasError() { + return Project{}, fmt.Errorf("error reading project git comments: %s - %s", diags[0].Summary(), diags[0].Detail()) + } + } + return Project{ - BuildCommand: uncoerceString(fields.BuildCommand, fromStringPointer(response.BuildCommand)), - DevCommand: uncoerceString(fields.DevCommand, fromStringPointer(response.DevCommand)), + BuildCommand: uncoerceString(fields.BuildCommand, types.StringPointerValue(response.BuildCommand)), + DevCommand: uncoerceString(fields.DevCommand, types.StringPointerValue(response.DevCommand)), Environment: environmentEntry, - Framework: fromStringPointer(response.Framework), + Framework: types.StringPointerValue(response.Framework), GitRepository: gr, ID: types.StringValue(response.ID), - IgnoreCommand: fromStringPointer(response.CommandForIgnoringBuildStep), - InstallCommand: uncoerceString(fields.InstallCommand, fromStringPointer(response.InstallCommand)), + IgnoreCommand: types.StringPointerValue(response.CommandForIgnoringBuildStep), + InstallCommand: uncoerceString(fields.InstallCommand, types.StringPointerValue(response.InstallCommand)), Name: types.StringValue(response.Name), - OutputDirectory: uncoerceString(fields.OutputDirectory, fromStringPointer(response.OutputDirectory)), - PublicSource: uncoerceBool(fields.PublicSource, fromBoolPointer(response.PublicSource)), - RootDirectory: fromStringPointer(response.RootDirectory), - ServerlessFunctionRegion: fromStringPointer(response.ServerlessFunctionRegion), + OutputDirectory: uncoerceString(fields.OutputDirectory, types.StringPointerValue(response.OutputDirectory)), + PublicSource: uncoerceBool(fields.PublicSource, types.BoolPointerValue(response.PublicSource)), + RootDirectory: types.StringPointerValue(response.RootDirectory), + ServerlessFunctionRegion: types.StringPointerValue(response.ServerlessFunctionRegion), TeamID: toTeamID(response.TeamID), PasswordProtection: pp, VercelAuthentication: va, TrustedIps: tip, ProtectionBypassForAutomation: protectionBypass, ProtectionBypassForAutomationSecret: protectionBypassSecret, - AutoExposeSystemEnvVars: fromBoolPointer(response.AutoExposeSystemEnvVars), + AutoExposeSystemEnvVars: types.BoolPointerValue(response.AutoExposeSystemEnvVars), + PreviewComments: types.BoolPointerValue(response.EnablePreviewFeedback), + AutoAssignCustomDomains: types.BoolValue(response.AutoAssignCustomDomains), + GitLFS: types.BoolValue(response.GitLFS), + FunctionFailover: types.BoolValue(response.ServerlessFunctionZeroConfigFailover), + CustomerSuccessCodeVisibility: types.BoolValue(response.CustomerSupportCodeVisibility), + GitForkProtection: types.BoolValue(response.GitForkProtection), + PrioritiseProductionBuilds: types.BoolValue(response.ProductionDeploymentsFastLane), + DirectoryListing: types.BoolValue(response.DirectoryListing), + SkewProtection: fromSkewProtectionMaxAge(response.SkewProtectionMaxAge), + GitComments: gitComments, }, nil } @@ -947,8 +1135,15 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest } } - if plan.PasswordProtection != nil || plan.VercelAuthentication != nil || plan.TrustedIps != nil || !plan.AutoExposeSystemEnvVars.IsNull() { - out, err = r.client.UpdateProject(ctx, result.ID.ValueString(), plan.TeamID.ValueString(), plan.toUpdateProjectRequest(plan.Name.ValueString())) + // Fields that have to be updated after the project is initially created. + if plan.RequiresUpdateAfterCreation() { + req, diags := plan.toUpdateProjectRequest(ctx, plan.Name.ValueString()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + out, err = r.client.UpdateProject(ctx, result.ID.ValueString(), plan.TeamID.ValueString(), req) if err != nil { resp.Diagnostics.AddError( "Error updating project as part of creating project", @@ -1280,7 +1475,12 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest } } - out, err := r.client.UpdateProject(ctx, state.ID.ValueString(), state.TeamID.ValueString(), plan.toUpdateProjectRequest(state.Name.ValueString())) + updateRequest, diags := plan.toUpdateProjectRequest(ctx, state.Name.ValueString()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + out, err := r.client.UpdateProject(ctx, state.ID.ValueString(), state.TeamID.ValueString(), updateRequest) if err != nil { resp.Diagnostics.AddError( "Error updating project", diff --git a/vercel/resource_project_domain.go b/vercel/resource_project_domain.go index b4dbf5ba..d5a7468d 100644 --- a/vercel/resource_project_domain.go +++ b/vercel/resource_project_domain.go @@ -112,11 +112,11 @@ type ProjectDomain struct { func convertResponseToProjectDomain(response client.ProjectDomainResponse) ProjectDomain { return ProjectDomain{ Domain: types.StringValue(response.Name), - GitBranch: fromStringPointer(response.GitBranch), + GitBranch: types.StringPointerValue(response.GitBranch), ID: types.StringValue(response.Name), ProjectID: types.StringValue(response.ProjectID), - Redirect: fromStringPointer(response.Redirect), - RedirectStatusCode: fromInt64Pointer(response.RedirectStatusCode), + Redirect: types.StringPointerValue(response.Redirect), + RedirectStatusCode: types.Int64PointerValue(response.RedirectStatusCode), TeamID: toTeamID(response.TeamID), } } @@ -132,9 +132,9 @@ func (p *ProjectDomain) toCreateRequest() client.CreateProjectDomainRequest { func (p *ProjectDomain) toUpdateRequest() client.UpdateProjectDomainRequest { return client.UpdateProjectDomainRequest{ - GitBranch: toStrPointer(p.GitBranch), - Redirect: toStrPointer(p.Redirect), - RedirectStatusCode: toInt64Pointer(p.RedirectStatusCode), + GitBranch: p.GitBranch.ValueStringPointer(), + Redirect: p.Redirect.ValueStringPointer(), + RedirectStatusCode: p.RedirectStatusCode.ValueInt64Pointer(), } } diff --git a/vercel/resource_project_environment_variable.go b/vercel/resource_project_environment_variable.go index cbf1e142..d02362f4 100644 --- a/vercel/resource_project_environment_variable.go +++ b/vercel/resource_project_environment_variable.go @@ -144,7 +144,7 @@ func (e *ProjectEnvironmentVariable) toCreateEnvironmentVariableRequest() client Key: e.Key.ValueString(), Value: e.Value.ValueString(), Target: target, - GitBranch: toStrPointer(e.GitBranch), + GitBranch: e.GitBranch.ValueStringPointer(), Type: envVariableType, }, ProjectID: e.ProjectID.ValueString(), @@ -169,7 +169,7 @@ func (e *ProjectEnvironmentVariable) toUpdateEnvironmentVariableRequest() client return client.UpdateEnvironmentVariableRequest{ Value: e.Value.ValueString(), Target: target, - GitBranch: toStrPointer(e.GitBranch), + GitBranch: e.GitBranch.ValueStringPointer(), Type: envVariableType, ProjectID: e.ProjectID.ValueString(), TeamID: e.TeamID.ValueString(), @@ -193,7 +193,7 @@ func convertResponseToProjectEnvironmentVariable(response client.EnvironmentVari return ProjectEnvironmentVariable{ Target: target, - GitBranch: fromStringPointer(response.GitBranch), + GitBranch: types.StringPointerValue(response.GitBranch), Key: types.StringValue(response.Key), Value: value, TeamID: toTeamID(response.TeamID), diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go index 9d23c705..6c35d63b 100644 --- a/vercel/resource_project_test.go +++ b/vercel/resource_project_test.go @@ -65,6 +65,17 @@ func TestAcc_Project(t *testing.T) { "value": "bar", }), resource.TestCheckTypeSetElemAttr("vercel_project.test", "environment.0.target.*", "production"), + resource.TestCheckResourceAttr("vercel_project.test", "git_comments.on_pull_request", "true"), + resource.TestCheckResourceAttr("vercel_project.test", "git_comments.on_commit", "true"), + resource.TestCheckResourceAttr("vercel_project.test", "preview_comments", "true"), + resource.TestCheckResourceAttr("vercel_project.test", "auto_assign_custom_domains", "true"), + resource.TestCheckResourceAttr("vercel_project.test", "git_lfs", "true"), + resource.TestCheckResourceAttr("vercel_project.test", "function_failover", "true"), + resource.TestCheckResourceAttr("vercel_project.test", "customer_success_code_visibility", "true"), + resource.TestCheckResourceAttr("vercel_project.test", "git_fork_protection", "true"), + resource.TestCheckResourceAttr("vercel_project.test", "prioritise_production_builds", "true"), + resource.TestCheckResourceAttr("vercel_project.test", "directory_listing", "true"), + resource.TestCheckResourceAttr("vercel_project.test", "skew_protection", "7 days"), ), }, // Update testing @@ -588,6 +599,20 @@ resource "vercel_project" "test" { public_source = true root_directory = "ui/src" automatically_expose_system_environment_variables = true + git_comments = { + on_pull_request = true, + on_commit = true + } + preview_comments = true + auto_assign_custom_domains = true + git_lfs = true + function_failover = true + customer_success_code_visibility = true + git_fork_protection = true + prioritise_production_builds = true + directory_listing = true + skew_protection = "7 days" + environment = [ { key = "foo" diff --git a/vercel/to_team_id.go b/vercel/to_team_id.go new file mode 100644 index 00000000..23a14025 --- /dev/null +++ b/vercel/to_team_id.go @@ -0,0 +1,10 @@ +package vercel + +import "github.com/hashicorp/terraform-plugin-framework/types" + +func toTeamID(v string) types.String { + if v == "" { + return types.StringNull() + } + return types.StringValue(v) +} diff --git a/vercel/types_conversions.go b/vercel/types_conversions.go deleted file mode 100644 index efe05eb4..00000000 --- a/vercel/types_conversions.go +++ /dev/null @@ -1,63 +0,0 @@ -package vercel - -import "github.com/hashicorp/terraform-plugin-framework/types" - -func toPtr[T any](v T) *T { - return &v -} - -func toStrPointer(v types.String) *string { - if v.IsNull() || v.IsUnknown() { - return nil - } - return toPtr(v.ValueString()) -} - -func toBoolPointer(v types.Bool) *bool { - if v.IsNull() || v.IsUnknown() { - return nil - } - return toPtr(v.ValueBool()) -} - -func toInt64Pointer(v types.Int64) *int64 { - if v.IsNull() || v.IsUnknown() { - return nil - } - return toPtr(v.ValueInt64()) -} - -func fromStringPointer(v *string) types.String { - if v == nil { - return types.StringNull() - } - return types.StringValue(*v) -} - -func fromBoolPointer(v *bool) types.Bool { - if v == nil { - return types.BoolNull() - } - return types.BoolValue(*v) -} - -func fromInt64Pointer(v *int64) types.Int64 { - if v == nil { - return types.Int64Null() - } - return types.Int64Value(*v) -} - -func fromFloat64Pointer(v *float64) types.Float64 { - if v == nil { - return types.Float64Null() - } - return types.Float64Value(*v) -} - -func toTeamID(v string) types.String { - if v == "" { - return types.StringNull() - } - return types.StringValue(v) -}