这是indexloc提供的服务,不要输入任何密码
Skip to content

Prevent project field coercion on creation #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/resources/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ 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.
- `public_source` (Boolean) Specifies whether the source code and logs of the deployments for this project should be public or not.
- `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.
Expand Down
2 changes: 1 addition & 1 deletion vercel/data_source_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func (r dataSourceProject) Read(ctx context.Context, req datasource.ReadRequest,
return
}

result := convertResponseToProject(out, config.TeamID)
result := convertResponseToProject(out, config.coercedFields())
tflog.Trace(ctx, "read project", map[string]interface{}{
"team_id": result.TeamID.Value,
"project_id": result.ID.Value,
Expand Down
22 changes: 13 additions & 9 deletions vercel/resource_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ For more detailed information, please see the [Vercel documentation](https://ver
"public_source": {
Optional: true,
Type: types.BoolType,
Description: "Specifies whether the source code and logs of the deployments for this project should be public or not.",
Description: "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": {
Optional: true,
Expand Down Expand Up @@ -223,7 +223,7 @@ func (r resourceProject) Create(ctx context.Context, req resource.CreateRequest,
return
}

result := convertResponseToProject(out, plan.TeamID)
result := convertResponseToProject(out, plan.coercedFields())
tflog.Trace(ctx, "created project", map[string]interface{}{
"team_id": result.TeamID.Value,
"project_id": result.ID.Value,
Expand Down Expand Up @@ -263,7 +263,7 @@ func (r resourceProject) Read(ctx context.Context, req resource.ReadRequest, res
return
}

result := convertResponseToProject(out, state.TeamID)
result := convertResponseToProject(out, state.coercedFields())
tflog.Trace(ctx, "read project", map[string]interface{}{
"team_id": result.TeamID.Value,
"project_id": result.ID.Value,
Expand Down Expand Up @@ -392,7 +392,7 @@ func (r resourceProject) Update(ctx context.Context, req resource.UpdateRequest,
return
}

result := convertResponseToProject(out, plan.TeamID)
result := convertResponseToProject(out, plan.coercedFields())
tflog.Trace(ctx, "updated project", map[string]interface{}{
"team_id": result.TeamID.Value,
"project_id": result.ID.Value,
Expand Down Expand Up @@ -476,11 +476,15 @@ func (r resourceProject) ImportState(ctx context.Context, req resource.ImportSta
return
}

stringTypeTeamID := types.String{Value: teamID}
if teamID == "" {
stringTypeTeamID.Null = true
}
result := convertResponseToProject(out, stringTypeTeamID)
result := convertResponseToProject(out, projectCoercedFields{
/* As this is import, none of these fields are specified - so treat them all as Null */
BuildCommand: types.String{Null: true},
DevCommand: types.String{Null: true},
InstallCommand: types.String{Null: true},
OutputDirectory: types.String{Null: true},
PublicSource: types.Bool{Null: true},
TeamID: types.String{Value: teamID, Null: teamID == ""},
})
tflog.Trace(ctx, "imported project", map[string]interface{}{
"team_id": result.TeamID.Value,
"project_id": result.ID.Value,
Expand Down
66 changes: 55 additions & 11 deletions vercel/resource_project_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,55 @@ func (g *GitRepository) toCreateProjectRequest() *client.GitRepository {
}
}

func convertResponseToProject(response client.ProjectResponse, tid types.String) Project {
/*
* In the Vercel API the following fields are coerced to null during project creation

* This causes an issue when they are specified, but falsy, as the
* terraform configuration explicitly sets a value for them, but the Vercel
* API returns a different value. This causes an inconsistent plan error.

* We avoid this issue by choosing to use values from the terraform state,
* but only if they are _explicitly stated_ *and* they are _falsy_ values
* *and* the response value was null. This is important as drift detection
* would fail to work if the value was always selected, so this is as stringent
* as possible to allow drift-detection in the majority of scenarios.

* This is implemented in the below uncoerceString and uncoerceBool functions.
*/
type projectCoercedFields struct {
BuildCommand types.String
DevCommand types.String
InstallCommand types.String
OutputDirectory types.String
PublicSource types.Bool
TeamID types.String
}

func (p *Project) coercedFields() projectCoercedFields {
return projectCoercedFields{
BuildCommand: p.BuildCommand,
DevCommand: p.DevCommand,
InstallCommand: p.InstallCommand,
OutputDirectory: p.OutputDirectory,
PublicSource: p.PublicSource,
TeamID: p.TeamID,
}
}

func uncoerceString(plan, res types.String) types.String {
if plan.Value == "" && !plan.Null && res.Null {
return plan
}
return res
}
func uncoerceBool(plan, res types.Bool) types.Bool {
if !plan.Value && !plan.Null && res.Null {
return plan
}
return res
}

func convertResponseToProject(response client.ProjectResponse, fields projectCoercedFields) Project {
var gr *GitRepository
if repo := response.Repository(); repo != nil {
gr = &GitRepository{
Expand All @@ -141,25 +189,21 @@ func convertResponseToProject(response client.ProjectResponse, tid types.String)
ID: types.String{Value: e.ID},
})
}
teamID := types.String{Value: tid.Value}
if tid.Unknown || tid.Null {
teamID.Null = true
}

return Project{
BuildCommand: fromStringPointer(response.BuildCommand),
DevCommand: fromStringPointer(response.DevCommand),
BuildCommand: uncoerceString(fields.BuildCommand, fromStringPointer(response.BuildCommand)),
DevCommand: uncoerceString(fields.DevCommand, fromStringPointer(response.DevCommand)),
Environment: env,
Framework: fromStringPointer(response.Framework),
GitRepository: gr,
ID: types.String{Value: response.ID},
IgnoreCommand: fromStringPointer(response.CommandForIgnoringBuildStep),
InstallCommand: fromStringPointer(response.InstallCommand),
InstallCommand: uncoerceString(fields.InstallCommand, fromStringPointer(response.InstallCommand)),
Name: types.String{Value: response.Name},
OutputDirectory: fromStringPointer(response.OutputDirectory),
PublicSource: fromBoolPointer(response.PublicSource),
OutputDirectory: uncoerceString(fields.OutputDirectory, fromStringPointer(response.OutputDirectory)),
PublicSource: uncoerceBool(fields.PublicSource, fromBoolPointer(response.PublicSource)),
RootDirectory: fromStringPointer(response.RootDirectory),
ServerlessFunctionRegion: fromStringPointer(response.ServerlessFunctionRegion),
TeamID: teamID,
TeamID: types.String{Value: fields.TeamID.Value, Null: fields.TeamID.Null || fields.TeamID.Unknown},
}
}
1 change: 1 addition & 0 deletions vercel/resource_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ func testAccProjectConfigWithGitRepoUpdated(projectSuffix, teamID string) string
resource "vercel_project" "test_git" {
name = "test-acc-two-%s"
%s
public_source = false
git_repository = {
type = "github"
repo = "%s"
Expand Down