diff --git a/client/environment_variable_create.go b/client/environment_variable_create.go
index d1d59d53..783dfa42 100644
--- a/client/environment_variable_create.go
+++ b/client/environment_variable_create.go
@@ -75,7 +75,7 @@ func (c *Client) CreateEnvironmentVariables(ctx context.Context, request CreateE
return err
}
- tflog.Trace(ctx, "creating environment variable", map[string]interface{}{
+ tflog.Trace(ctx, "creating environment variables", map[string]interface{}{
"url": url,
"payload": payload,
})
diff --git a/client/project_get.go b/client/project_get.go
index 9bc43002..e4e03be6 100644
--- a/client/project_get.go
+++ b/client/project_get.go
@@ -55,6 +55,10 @@ func (r *ProjectResponse) Repository() *Repository {
return nil
}
+type Protection struct {
+ DeploymentType string `json:"deploymentType"`
+}
+
// ProjectResponse defines the information Vercel returns about a project.
type ProjectResponse struct {
BuildCommand *string `json:"buildCommand"`
@@ -80,11 +84,13 @@ type ProjectResponse struct {
// production branch
ProductionBranch *string `json:"productionBranch"`
} `json:"link"`
- Name string `json:"name"`
- OutputDirectory *string `json:"outputDirectory"`
- PublicSource *bool `json:"publicSource"`
- RootDirectory *string `json:"rootDirectory"`
- ServerlessFunctionRegion *string `json:"serverlessFunctionRegion"`
+ Name string `json:"name"`
+ OutputDirectory *string `json:"outputDirectory"`
+ PublicSource *bool `json:"publicSource"`
+ RootDirectory *string `json:"rootDirectory"`
+ ServerlessFunctionRegion *string `json:"serverlessFunctionRegion"`
+ SSOProtection *Protection `json:"ssoProtection"`
+ PasswordProtection *Protection `json:"passwordProtection"`
}
// GetProject retrieves information about an existing project from Vercel.
diff --git a/client/project_update.go b/client/project_update.go
index 27e9bb12..08e8ef42 100644
--- a/client/project_update.go
+++ b/client/project_update.go
@@ -9,23 +9,30 @@ import (
"github.com/hashicorp/terraform-plugin-log/tflog"
)
+type PasswordProtectionRequest struct {
+ DeploymentType string `json:"deploymentType"`
+ Password string `json:"password"`
+}
+
// UpdateProjectRequest defines the possible fields that can be updated within a vercel project.
// note that the values are all pointers, with many containing `omitempty` for serialisation.
// This is because the Vercel API behaves in the following manner:
// - a provided field will be updated
-// - setting the field to an empty value (e.g. ”) will remove the setting for that field.
+// - 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"`
+ 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"`
+ SSOProtection *Protection `json:"ssoProtection"`
+ PasswordProtection *PasswordProtectionRequest `json:"passwordProtection"`
}
// UpdateProject updates an existing projects configuration within Vercel.
diff --git a/docs/data-sources/project.md b/docs/data-sources/project.md
index d60611f6..f2440ed4 100644
--- a/docs/data-sources/project.md
+++ b/docs/data-sources/project.md
@@ -38,6 +38,7 @@ output "project_id" {
### Optional
+- `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))
- `team_id` (String) The team ID the project exists beneath.
### Read-Only
@@ -54,6 +55,15 @@ output "project_id" {
- `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.
+- `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))
+
+
+### Nested Schema for `password_protection`
+
+Read-Only:
+
+- `protect_production` (Boolean) If true, production deployments will also be protected
+
### Nested Schema for `environment`
@@ -77,3 +87,11 @@ Read-Only:
- `type` (String) The git provider of the repository. Must be either `github`, `gitlab`, or `bitbucket`.
+
+### Nested Schema for `vercel_authentication`
+
+Read-Only:
+
+- `protect_production` (Boolean) If true, production deployments will also be protected
+
+
diff --git a/docs/resources/project.md b/docs/resources/project.md
index 57cec7ce..6f2f60b4 100644
--- a/docs/resources/project.md
+++ b/docs/resources/project.md
@@ -63,10 +63,12 @@ resource "vercel_project" "example" {
- `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))
- `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.
- `team_id` (String) The team ID to add the project to.
+- `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))
### Read-Only
@@ -102,6 +104,26 @@ Optional:
- `production_branch` (String) 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.
+
+
+### Nested Schema for `password_protection`
+
+Required:
+
+- `password` (String, Sensitive) The password that visitors must enter to gain access to your Preview Deployments. Drift detection is not possible for this field.
+
+Optional:
+
+- `protect_production` (Boolean) If true, production deployments will also be protected
+
+
+
+### Nested Schema for `vercel_authentication`
+
+Optional:
+
+- `protect_production` (Boolean) If true, production deployments will also be protected
+
## Import
Import is supported using the following syntax:
diff --git a/vercel/data_source_alias.go b/vercel/data_source_alias.go
index 59d9a0a9..3af2f7a0 100644
--- a/vercel/data_source_alias.go
+++ b/vercel/data_source_alias.go
@@ -10,6 +10,11 @@ import (
"github.com/vercel/terraform-provider-vercel/client"
)
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &aliasDataSource{}
+)
+
func newAliasDataSource() datasource.DataSource {
return &aliasDataSource{}
}
diff --git a/vercel/data_source_file.go b/vercel/data_source_file.go
index 05519206..bfa16d6e 100644
--- a/vercel/data_source_file.go
+++ b/vercel/data_source_file.go
@@ -13,6 +13,11 @@ import (
"github.com/vercel/terraform-provider-vercel/client"
)
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &fileDataSource{}
+)
+
func newFileDataSource() datasource.DataSource {
return &fileDataSource{}
}
diff --git a/vercel/data_source_prebuilt_project.go b/vercel/data_source_prebuilt_project.go
index 46643388..0edb5a98 100644
--- a/vercel/data_source_prebuilt_project.go
+++ b/vercel/data_source_prebuilt_project.go
@@ -16,6 +16,11 @@ import (
"github.com/vercel/terraform-provider-vercel/file"
)
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &prebuiltProjectDataSource{}
+)
+
func newPrebuiltProjectDataSource() datasource.DataSource {
return &prebuiltProjectDataSource{}
}
diff --git a/vercel/data_source_project.go b/vercel/data_source_project.go
index 7272ae7d..6cf602fb 100644
--- a/vercel/data_source_project.go
+++ b/vercel/data_source_project.go
@@ -13,6 +13,11 @@ import (
"github.com/vercel/terraform-provider-vercel/client"
)
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &projectDataSource{}
+)
+
func newProjectDataSource() datasource.DataSource {
return &projectDataSource{}
}
@@ -140,6 +145,26 @@ For more detailed information, please see the [Vercel documentation](https://ver
},
},
},
+ "vercel_authentication": schema.SingleNestedAttribute{
+ Description: "Ensures visitors to your Preview Deployments are logged into Vercel and have a minimum of Viewer access on your team.",
+ Computed: true,
+ Attributes: map[string]schema.Attribute{
+ "protect_production": schema.BoolAttribute{
+ Description: "If true, production deployments will also be protected",
+ Computed: true,
+ },
+ },
+ },
+ "password_protection": schema.SingleNestedAttribute{
+ Description: "Ensures visitors of your Preview Deployments must enter a password in order to gain access.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "protect_production": schema.BoolAttribute{
+ Description: "If true, production deployments will also be protected",
+ Computed: true,
+ },
+ },
+ },
"id": schema.StringAttribute{
Computed: true,
},
@@ -167,7 +192,7 @@ For more detailed information, please see the [Vercel documentation](https://ver
// with this information.
// It is called by the provider whenever data source values should be read to update state.
func (d *projectDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
- var config Project
+ var config ProjectDataSource
diags := req.Config.Get(ctx, &config)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
@@ -187,7 +212,7 @@ func (d *projectDataSource) Read(ctx context.Context, req datasource.ReadRequest
return
}
- result := convertResponseToProject(out, config.coercedFields(), types.SetNull(envVariableElemType))
+ result := convertResponseToProjectDataSource(out, nullProject)
tflog.Trace(ctx, "read project", map[string]interface{}{
"team_id": result.TeamID.ValueString(),
"project_id": result.ID.ValueString(),
diff --git a/vercel/data_source_project_directory.go b/vercel/data_source_project_directory.go
index d173fbad..ac114ef3 100644
--- a/vercel/data_source_project_directory.go
+++ b/vercel/data_source_project_directory.go
@@ -14,6 +14,11 @@ import (
"github.com/vercel/terraform-provider-vercel/file"
)
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ datasource.DataSource = &projectDirectoryDataSource{}
+)
+
func newProjectDirectoryDataSource() datasource.DataSource {
return &projectDirectoryDataSource{}
}
diff --git a/vercel/data_source_project_model.go b/vercel/data_source_project_model.go
new file mode 100644
index 00000000..451807cb
--- /dev/null
+++ b/vercel/data_source_project_model.go
@@ -0,0 +1,59 @@
+package vercel
+
+import (
+ "github.com/hashicorp/terraform-plugin-framework/types"
+ "github.com/vercel/terraform-provider-vercel/client"
+)
+
+// 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 *PasswordProtectionDataSource `tfsdk:"password_protection"`
+}
+
+type PasswordProtectionDataSource struct {
+ ProtectProduction types.Bool `tfsdk:"protect_production"`
+}
+
+func convertResponseToProjectDataSource(response client.ProjectResponse, plan Project) ProjectDataSource {
+ project := convertResponseToProject(response, plan)
+
+ var pp *PasswordProtectionDataSource
+ if project.PasswordProtection != nil {
+ pp = &PasswordProtectionDataSource{
+ ProtectProduction: project.PasswordProtection.ProtectProduction,
+ }
+ }
+ 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,
+ }
+}
diff --git a/vercel/data_source_project_test.go b/vercel/data_source_project_test.go
index afcadca7..b199f751 100644
--- a/vercel/data_source_project_test.go
+++ b/vercel/data_source_project_test.go
@@ -25,6 +25,8 @@ func TestAcc_ProjectDataSource(t *testing.T) {
resource.TestCheckResourceAttr("data.vercel_project.test", "output_directory", ".output"),
resource.TestCheckResourceAttr("data.vercel_project.test", "public_source", "true"),
resource.TestCheckResourceAttr("data.vercel_project.test", "root_directory", "ui/src"),
+ resource.TestCheckResourceAttr("data.vercel_project.test", "vercel_authentication.protect_production", "true"),
+ resource.TestCheckResourceAttr("data.vercel_project.test", "password_protection.protect_production", "true"),
resource.TestCheckTypeSetElemNestedAttrs("data.vercel_project.test", "environment.*", map[string]string{
"key": "foo",
"value": "bar",
@@ -47,6 +49,13 @@ resource "vercel_project" "test" {
output_directory = ".output"
public_source = true
root_directory = "ui/src"
+ vercel_authentication = {
+ protect_production = true
+ }
+ password_protection = {
+ password = "foo"
+ protect_production = true
+ }
%s
environment = [
{
diff --git a/vercel/resource_alias.go b/vercel/resource_alias.go
index 58418263..c091ee24 100644
--- a/vercel/resource_alias.go
+++ b/vercel/resource_alias.go
@@ -12,6 +12,12 @@ import (
"github.com/vercel/terraform-provider-vercel/client"
)
+// Ensure the implementation satisfies the expected interfaces.
+var (
+ _ resource.Resource = &aliasResource{}
+ _ resource.ResourceWithConfigure = &aliasResource{}
+)
+
func newAliasResource() resource.Resource {
return &aliasResource{}
}
diff --git a/vercel/resource_deployment.go b/vercel/resource_deployment.go
index bcd9abcb..d2d81f2d 100644
--- a/vercel/resource_deployment.go
+++ b/vercel/resource_deployment.go
@@ -23,6 +23,11 @@ import (
"github.com/vercel/terraform-provider-vercel/file"
)
+var (
+ _ resource.Resource = &deploymentResource{}
+ _ resource.ResourceWithConfigure = &deploymentResource{}
+)
+
func newDeploymentResource() resource.Resource {
return &deploymentResource{}
}
diff --git a/vercel/resource_dns_record.go b/vercel/resource_dns_record.go
index 7a2903d1..52b9a5f1 100644
--- a/vercel/resource_dns_record.go
+++ b/vercel/resource_dns_record.go
@@ -14,6 +14,11 @@ import (
"github.com/vercel/terraform-provider-vercel/client"
)
+var (
+ _ resource.Resource = &dnsRecordResource{}
+ _ resource.ResourceWithConfigure = &dnsRecordResource{}
+)
+
func newDNSRecordResource() resource.Resource {
return &dnsRecordResource{}
}
diff --git a/vercel/resource_project.go b/vercel/resource_project.go
index f82ded56..c29b9314 100644
--- a/vercel/resource_project.go
+++ b/vercel/resource_project.go
@@ -8,6 +8,7 @@ import (
"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/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
@@ -16,6 +17,11 @@ import (
"github.com/vercel/terraform-provider-vercel/client"
)
+var (
+ _ resource.Resource = &projectResource{}
+ _ resource.ResourceWithConfigure = &projectResource{}
+)
+
func newProjectResource() resource.Resource {
return &projectResource{}
}
@@ -162,6 +168,38 @@ At this time you cannot use a Vercel Project resource with in-line ` + "`environ
},
},
},
+ "vercel_authentication": schema.SingleNestedAttribute{
+ Description: "Ensures visitors to your Preview Deployments are logged into Vercel and have a minimum of Viewer access on your team.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "protect_production": schema.BoolAttribute{
+ Description: "If true, production deployments will also be protected",
+ Optional: true,
+ Computed: true,
+ Default: booldefault.StaticBool(false),
+ },
+ },
+ },
+ "password_protection": schema.SingleNestedAttribute{
+ Description: "Ensures visitors of your Preview Deployments must enter a password in order to gain access.",
+ Optional: true,
+ Attributes: map[string]schema.Attribute{
+ "password": schema.StringAttribute{
+ Description: "The password that visitors must enter to gain access to your Preview Deployments. Drift detection is not possible for this field.",
+ Required: true,
+ Sensitive: true,
+ Validators: []validator.String{
+ stringLengthBetween(1, 72),
+ },
+ },
+ "protect_production": schema.BoolAttribute{
+ Description: "If true, production deployments will also be protected",
+ Optional: true,
+ Computed: true,
+ Default: booldefault.StaticBool(false),
+ },
+ },
+ },
"id": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
@@ -214,7 +252,7 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
return
}
- result := convertResponseToProject(out, plan.coercedFields(), plan.Environment)
+ result := convertResponseToProject(out, plan)
tflog.Trace(ctx, "created project", map[string]interface{}{
"team_id": result.TeamID.ValueString(),
"project_id": result.ID.ValueString(),
@@ -225,6 +263,28 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
return
}
+ if plan.PasswordProtection != nil || plan.VercelAuthentication != nil {
+ out, err = r.client.UpdateProject(ctx, result.ID.ValueString(), plan.TeamID.ValueString(), plan.toUpdateProjectRequest(plan.Name.ValueString()), !plan.Environment.IsNull())
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error updating project as part of creating project",
+ "Could not update project, unexpected error: "+err.Error(),
+ )
+ return
+ }
+
+ result := convertResponseToProject(out, plan)
+ tflog.Trace(ctx, "updated newly created project", map[string]interface{}{
+ "team_id": result.TeamID.ValueString(),
+ "project_id": result.ID.ValueString(),
+ })
+ diags = resp.State.Set(ctx, result)
+ resp.Diagnostics.Append(diags...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+ }
+
if plan.GitRepository == nil || plan.GitRepository.ProductionBranch.IsNull() || plan.GitRepository.ProductionBranch.IsUnknown() {
return
}
@@ -242,7 +302,7 @@ func (r *projectResource) Create(ctx context.Context, req resource.CreateRequest
return
}
- result = convertResponseToProject(out, plan.coercedFields(), plan.Environment)
+ result = convertResponseToProject(out, plan)
tflog.Trace(ctx, "updated project production branch", map[string]interface{}{
"team_id": result.TeamID.ValueString(),
"project_id": result.ID.ValueString(),
@@ -282,7 +342,7 @@ func (r *projectResource) Read(ctx context.Context, req resource.ReadRequest, re
return
}
- result := convertResponseToProject(out, state.coercedFields(), state.Environment)
+ result := convertResponseToProject(out, state)
tflog.Trace(ctx, "read project", map[string]interface{}{
"team_id": result.TeamID.ValueString(),
"project_id": result.ID.ValueString(),
@@ -368,7 +428,7 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest
return
}
- tflog.Error(ctx, "planEnvs", map[string]interface{}{
+ tflog.Trace(ctx, "planEnvs", map[string]interface{}{
"plan_envs": planEnvs,
"state_envs": stateEnvs,
})
@@ -400,28 +460,30 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest
items = append(items, v.toEnvironmentVariableRequest())
}
- err = r.client.CreateEnvironmentVariables(
- ctx,
- client.CreateEnvironmentVariablesRequest{
- ProjectID: plan.ID.ValueString(),
- TeamID: plan.TeamID.ValueString(),
- EnvironmentVariables: items,
- },
- )
- if err != nil {
- resp.Diagnostics.AddError(
- "Error updating project",
- fmt.Sprintf(
- "Could not upsert environment variables for project %s, unexpected error: %s",
- plan.ID.ValueString(),
- err,
- ),
+ if items != nil {
+ err = r.client.CreateEnvironmentVariables(
+ ctx,
+ client.CreateEnvironmentVariablesRequest{
+ ProjectID: plan.ID.ValueString(),
+ TeamID: plan.TeamID.ValueString(),
+ EnvironmentVariables: items,
+ },
)
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "Error updating project",
+ fmt.Sprintf(
+ "Could not upsert environment variables for project %s, unexpected error: %s",
+ plan.ID.ValueString(),
+ err,
+ ),
+ )
+ }
+ tflog.Trace(ctx, "upserted environment variables", map[string]interface{}{
+ "team_id": plan.TeamID.ValueString(),
+ "project_id": plan.ID.ValueString(),
+ })
}
- tflog.Trace(ctx, "upserted environment variables", map[string]interface{}{
- "team_id": plan.TeamID.ValueString(),
- "project_id": plan.ID.ValueString(),
- })
out, err := r.client.UpdateProject(ctx, state.ID.ValueString(), state.TeamID.ValueString(), plan.toUpdateProjectRequest(state.Name.ValueString()), !plan.Environment.IsNull())
if err != nil {
@@ -457,7 +519,7 @@ func (r *projectResource) Update(ctx context.Context, req resource.UpdateRequest
}
}
- result := convertResponseToProject(out, plan.coercedFields(), plan.Environment)
+ result := convertResponseToProject(out, plan)
tflog.Trace(ctx, "updated project", map[string]interface{}{
"team_id": result.TeamID.ValueString(),
"project_id": result.ID.ValueString(),
@@ -540,14 +602,7 @@ func (r *projectResource) ImportState(ctx context.Context, req resource.ImportSt
return
}
- result := convertResponseToProject(out, projectCoercedFields{
- /* As this is import, none of these fields are specified - so treat them all as Null */
- BuildCommand: types.StringNull(),
- DevCommand: types.StringNull(),
- InstallCommand: types.StringNull(),
- OutputDirectory: types.StringNull(),
- PublicSource: types.BoolNull(),
- }, types.SetNull(envVariableElemType))
+ result := convertResponseToProject(out, nullProject)
tflog.Trace(ctx, "imported project", map[string]interface{}{
"team_id": result.TeamID.ValueString(),
"project_id": result.ID.ValueString(),
diff --git a/vercel/resource_project_domain.go b/vercel/resource_project_domain.go
index 45e1e635..53f910b3 100644
--- a/vercel/resource_project_domain.go
+++ b/vercel/resource_project_domain.go
@@ -14,6 +14,11 @@ import (
"github.com/vercel/terraform-provider-vercel/client"
)
+var (
+ _ resource.Resource = &projectDomainResource{}
+ _ resource.ResourceWithConfigure = &projectDomainResource{}
+)
+
func newProjectDomainResource() resource.Resource {
return &projectDomainResource{}
}
diff --git a/vercel/resource_project_environment_variable.go b/vercel/resource_project_environment_variable.go
index adeb9046..7f7da208 100644
--- a/vercel/resource_project_environment_variable.go
+++ b/vercel/resource_project_environment_variable.go
@@ -15,6 +15,11 @@ import (
"github.com/vercel/terraform-provider-vercel/client"
)
+var (
+ _ resource.Resource = &projectEnvironmentVariableResource{}
+ _ resource.ResourceWithConfigure = &projectEnvironmentVariableResource{}
+)
+
func newProjectEnvironmentVariableResource() resource.Resource {
return &projectEnvironmentVariableResource{}
}
diff --git a/vercel/resource_project_model.go b/vercel/resource_project_model.go
index 13738717..e0644dcc 100644
--- a/vercel/resource_project_model.go
+++ b/vercel/resource_project_model.go
@@ -11,20 +11,32 @@ import (
// Project reflects the state terraform stores internally for a project.
type Project 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"`
+ 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"`
+}
+
+var nullProject = Project{
+ /* As this is read only, none of these fields are specified - so treat them all as Null */
+ BuildCommand: types.StringNull(),
+ DevCommand: types.StringNull(),
+ InstallCommand: types.StringNull(),
+ OutputDirectory: types.StringNull(),
+ PublicSource: types.BoolNull(),
+ Environment: types.SetNull(envVariableElemType),
}
func (p *Project) environment(ctx context.Context) ([]EnvironmentItem, error) {
@@ -94,6 +106,8 @@ func (p *Project) toUpdateProjectRequest(oldName string) client.UpdateProjectReq
PublicSource: toBoolPointer(p.PublicSource),
RootDirectory: toStrPointer(p.RootDirectory),
ServerlessFunctionRegion: toStrPointer(p.ServerlessFunctionRegion),
+ PasswordProtection: p.PasswordProtection.toUpdateProjectRequest(),
+ SSOProtection: p.VercelAuthentication.toUpdateProjectRequest(),
}
}
@@ -137,6 +151,46 @@ func (g *GitRepository) toCreateProjectRequest() *client.GitRepository {
}
}
+type VercelAuthentication struct {
+ ProtectProduction types.Bool `tfsdk:"protect_production"`
+}
+
+func (v *VercelAuthentication) toUpdateProjectRequest() *client.Protection {
+ if v == nil {
+ return nil
+ }
+
+ deploymentType := "preview"
+ if v.ProtectProduction.ValueBool() {
+ deploymentType = "all"
+ }
+
+ return &client.Protection{
+ DeploymentType: deploymentType,
+ }
+}
+
+type PasswordProtection struct {
+ Password types.String `tfsdk:"password"`
+ ProtectProduction types.Bool `tfsdk:"protect_production"`
+}
+
+func (p *PasswordProtection) toUpdateProjectRequest() *client.PasswordProtectionRequest {
+ if p == nil {
+ return nil
+ }
+
+ deploymentType := "preview"
+ if p.ProtectProduction.ValueBool() {
+ deploymentType = "all"
+ }
+
+ return &client.PasswordProtectionRequest{
+ DeploymentType: deploymentType,
+ Password: p.Password.ValueString(),
+ }
+}
+
/*
* In the Vercel API the following fields are coerced to null during project creation
@@ -195,7 +249,9 @@ var envVariableElemType = types.ObjectType{
},
}
-func convertResponseToProject(response client.ProjectResponse, fields projectCoercedFields, environment types.Set) Project {
+func convertResponseToProject(response client.ProjectResponse, plan Project) Project {
+ fields := plan.coercedFields()
+
var gr *GitRepository
if repo := response.Repository(); repo != nil {
gr = &GitRepository{
@@ -208,6 +264,25 @@ func convertResponseToProject(response client.ProjectResponse, fields projectCoe
}
}
+ var pp *PasswordProtection
+ if response.PasswordProtection != nil {
+ pass := types.StringValue("")
+ if plan.PasswordProtection != nil {
+ pass = plan.PasswordProtection.Password
+ }
+ pp = &PasswordProtection{
+ Password: pass,
+ ProtectProduction: types.BoolValue(response.PasswordProtection.DeploymentType == "all"),
+ }
+ }
+
+ var va *VercelAuthentication
+ if response.SSOProtection != nil {
+ va = &VercelAuthentication{
+ ProtectProduction: types.BoolValue(response.SSOProtection.DeploymentType == "all"),
+ }
+ }
+
var env []attr.Value
for _, e := range response.EnvironmentVariables {
target := []attr.Value{}
@@ -235,7 +310,7 @@ func convertResponseToProject(response client.ProjectResponse, fields projectCoe
}
environmentEntry := types.SetValueMust(envVariableElemType, env)
- if len(response.EnvironmentVariables) == 0 && environment.IsNull() {
+ if len(response.EnvironmentVariables) == 0 && plan.Environment.IsNull() {
environmentEntry = types.SetNull(envVariableElemType)
}
@@ -254,5 +329,7 @@ func convertResponseToProject(response client.ProjectResponse, fields projectCoe
RootDirectory: fromStringPointer(response.RootDirectory),
ServerlessFunctionRegion: fromStringPointer(response.ServerlessFunctionRegion),
TeamID: toTeamID(response.TeamID),
+ PasswordProtection: pp,
+ VercelAuthentication: va,
}
}
diff --git a/vercel/resource_project_test.go b/vercel/resource_project_test.go
index 54a28f0f..e113a9e7 100644
--- a/vercel/resource_project_test.go
+++ b/vercel/resource_project_test.go
@@ -139,6 +139,48 @@ func TestAcc_ProjectWithGitRepository(t *testing.T) {
})
}
+func TestAcc_ProjectWithSSOAndPasswordProtection(t *testing.T) {
+ projectSuffix := acctest.RandString(16)
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
+ CheckDestroy: testAccProjectDestroy("vercel_project.enabled_to_start", testTeam()),
+ Steps: []resource.TestStep{
+ {
+ Config: testAccProjectConfigWithSSOAndPassword(projectSuffix, teamIDConfig()),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ testAccProjectExists("vercel_project.enabled_to_start", testTeam()),
+ resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "vercel_authentication.protect_production", "true"),
+ resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "password_protection.protect_production", "true"),
+ resource.TestCheckResourceAttr("vercel_project.enabled_to_start", "password_protection.password", "password"),
+ testAccProjectExists("vercel_project.disabled_to_start", testTeam()),
+ resource.TestCheckNoResourceAttr("vercel_project.disabled_to_start", "vercel_authentication"),
+ resource.TestCheckNoResourceAttr("vercel_project.disabled_to_start", "password_protection"),
+ testAccProjectExists("vercel_project.enabled_to_update", testTeam()),
+ resource.TestCheckResourceAttr("vercel_project.enabled_to_update", "vercel_authentication.protect_production", "false"),
+ resource.TestCheckResourceAttr("vercel_project.enabled_to_update", "password_protection.protect_production", "false"),
+ resource.TestCheckResourceAttr("vercel_project.enabled_to_update", "password_protection.password", "password"),
+ ),
+ },
+ {
+ Config: testAccProjectConfigWithSSOAndPasswordUpdated(projectSuffix, teamIDConfig()),
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckNoResourceAttr("vercel_project.enabled_to_start", "vercel_authentication"),
+ resource.TestCheckNoResourceAttr("vercel_project.enabled_to_start", "password_protection"),
+
+ resource.TestCheckResourceAttr("vercel_project.disabled_to_start", "vercel_authentication.protect_production", "true"),
+ resource.TestCheckResourceAttr("vercel_project.disabled_to_start", "password_protection.protect_production", "true"),
+ resource.TestCheckResourceAttr("vercel_project.disabled_to_start", "password_protection.password", "password"),
+
+ resource.TestCheckResourceAttr("vercel_project.enabled_to_update", "vercel_authentication.protect_production", "true"),
+ resource.TestCheckResourceAttr("vercel_project.enabled_to_update", "password_protection.protect_production", "true"),
+ resource.TestCheckResourceAttr("vercel_project.enabled_to_update", "password_protection.password", "password2"),
+ ),
+ },
+ },
+ })
+}
+
func getProjectImportID(n string) resource.ImportStateIdFunc {
return func(s *terraform.State) (string, error) {
rs, ok := s.RootModule().Resources[n]
@@ -269,6 +311,72 @@ resource "vercel_project" "test" {
`, projectSuffix, teamID)
}
+func testAccProjectConfigWithSSOAndPassword(projectSuffix, teamID string) string {
+ return fmt.Sprintf(`
+resource "vercel_project" "enabled_to_start" {
+ name = "test-acc-protection-one-%[1]s"
+ %[2]s
+ vercel_authentication = {
+ protect_production = true
+ }
+ password_protection = {
+ protect_production = true
+ password = "password"
+ }
+}
+
+resource "vercel_project" "disabled_to_start" {
+ name = "test-acc-protection-two-%[1]s"
+ %[2]s
+}
+
+resource "vercel_project" "enabled_to_update" {
+ name = "test-acc-protection-three-%[1]s"
+ %[2]s
+ vercel_authentication = {
+ protect_production = false
+ }
+ password_protection = {
+ protect_production = false
+ password = "password"
+ }
+}
+ `, projectSuffix, teamID)
+}
+
+func testAccProjectConfigWithSSOAndPasswordUpdated(projectSuffix, teamID string) string {
+ return fmt.Sprintf(`
+resource "vercel_project" "enabled_to_start" {
+ name = "test-acc-protection-one-%[1]s"
+ %[2]s
+}
+
+resource "vercel_project" "disabled_to_start" {
+ name = "test-acc-protection-two-%[1]s"
+ %[2]s
+ vercel_authentication = {
+ protect_production = true
+ }
+ password_protection = {
+ protect_production = true
+ password = "password"
+ }
+}
+
+resource "vercel_project" "enabled_to_update" {
+ name = "test-acc-protection-three-%[1]s"
+ %[2]s
+ vercel_authentication = {
+ protect_production = true
+ }
+ password_protection = {
+ protect_production = true
+ password = "password2"
+ }
+}
+ `, projectSuffix, teamID)
+}
+
func testAccProjectConfigWithGitRepo(projectSuffix, teamID string) string {
return fmt.Sprintf(`
resource "vercel_project" "test_git" {
diff --git a/vercel/resource_shared_environment_variable.go b/vercel/resource_shared_environment_variable.go
index 9e5573f3..95bde157 100644
--- a/vercel/resource_shared_environment_variable.go
+++ b/vercel/resource_shared_environment_variable.go
@@ -15,6 +15,11 @@ import (
"github.com/vercel/terraform-provider-vercel/client"
)
+var (
+ _ resource.Resource = &sharedEnvironmentVariableResource{}
+ _ resource.ResourceWithConfigure = &sharedEnvironmentVariableResource{}
+)
+
func newSharedEnvironmentVariableResource() resource.Resource {
return &sharedEnvironmentVariableResource{}
}