diff --git a/UPDATING_OPENAPI_JSON.md b/UPDATING_OPENAPI_JSON.md index 6f625e8..efd3631 100644 --- a/UPDATING_OPENAPI_JSON.md +++ b/UPDATING_OPENAPI_JSON.md @@ -11,7 +11,10 @@ This project uses a modified `openapi.json`. Please maintain these instructions 1. Remove the `domain` property from the `required` array of the `DnsZone` object. 1. Remove the `values`, `scopes` and `is_secret` parameters from the `updateEnvVar` operation. 1. Add a request body schema to the `updateEnvVar` operation, by copying it from an earlier version of the `openapi.json`. -1. Add a `package_path` property of type string to the `Repo` object. +1. Add a `package_path` property of type `string` to the `Repo` object. +1. Add a `functions_region` property of type `string` to the `Site` object. +1. Add a `cdp_enabled_contexts` property of type `array` of `string`s to the `Site` object. +1. Add a `hud_enabled` property of type `boolean` to the `Site` object. 1. Duplicate the `Site` object into `PartialSite` and remove the `required` properties. 1. Change `updateSite` operation to use the `PartialSite` object as the request body schema (NOTE: not the response body schema). 1. Change the type of `LogDrain.id` to `string`. diff --git a/docs/resources/site_deploy_settings.md b/docs/resources/site_deploy_settings.md new file mode 100644 index 0000000..402d479 --- /dev/null +++ b/docs/resources/site_deploy_settings.md @@ -0,0 +1,39 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "netlify_site_deploy_settings Resource - netlify" +subcategory: "" +description: |- + +--- + +# netlify_site_deploy_settings (Resource) + + + + + + +## Schema + +### Required + +- `production_branch` (String) +- `site_id` (String) + +### Optional + +- `branch_deploy_all_branches` (Boolean) +- `branch_deploy_branches` (List of String) +- `branch_deploy_custom_domain` (String) +- `custom_domain` (String) +- `deploy_preview_custom_domain` (String) +- `deploy_previews` (Boolean) +- `domain_aliases` (List of String) +- `functions_region` (String) +- `netlify_drawer_in_branch_deploys` (Boolean) +- `netlify_drawer_in_deploy_previews` (Boolean) +- `netlify_heads_up_display` (Boolean) + +### Read-Only + +- `last_updated` (String) diff --git a/examples/site_settings/main.tf b/examples/site_settings/main.tf index 01cab0f..aaa8931 100644 --- a/examples/site_settings/main.tf +++ b/examples/site_settings/main.tf @@ -20,3 +20,13 @@ resource "netlify_site_build_settings" "platform_test" { build_command = "npm run build" publish_directory = "dist" } + +resource "netlify_site_deploy_settings" "platform_test" { + site_id = data.netlify_site.platform_test.id + production_branch = "main" + branch_deploy_branches = ["meow", "woof"] + custom_domain = "platform-test.example-tf-test-test.com" + domain_aliases = ["meow.example-tf-test-test.com"] + branch_deploy_custom_domain = "branch.example-tf-test-test.com" + deploy_preview_custom_domain = "dp.example-tf-test-test.com" +} diff --git a/internal/netlifyapi/api/openapi.yaml b/internal/netlifyapi/api/openapi.yaml index 8f7f807..a6f10de 100644 --- a/internal/netlifyapi/api/openapi.yaml +++ b/internal/netlifyapi/api/openapi.yaml @@ -10311,8 +10311,12 @@ components: id_domain: id_domain deploy_url: deploy_url deploy_hook: deploy_hook + cdp_enabled_contexts: + - cdp_enabled_contexts + - cdp_enabled_contexts created_at: 2000-01-23T04:56:07.000+00:00 sso_login_context: sso_login_context + functions_region: functions_region domain_aliases: - domain_aliases - domain_aliases @@ -10331,6 +10335,7 @@ components: force_ssl: true notification_email: notification_email capabilities: "{}" + hud_enabled: true admin_url: admin_url build_settings: repo_type: repo_type @@ -10464,6 +10469,14 @@ components: items: $ref: '#/components/schemas/SiteLabel' type: array + functions_region: + type: string + cdp_enabled_contexts: + items: + type: string + type: array + hud_enabled: + type: boolean required: - account_id - account_name @@ -10508,8 +10521,12 @@ components: id_domain: id_domain deploy_url: deploy_url deploy_hook: deploy_hook + cdp_enabled_contexts: + - cdp_enabled_contexts + - cdp_enabled_contexts created_at: 2000-01-23T04:56:07.000+00:00 sso_login_context: sso_login_context + functions_region: functions_region domain_aliases: - domain_aliases - domain_aliases @@ -10528,6 +10545,7 @@ components: force_ssl: true notification_email: notification_email capabilities: "{}" + hud_enabled: true admin_url: admin_url build_settings: repo_type: repo_type @@ -10661,6 +10679,14 @@ components: items: $ref: '#/components/schemas/SiteLabel' type: array + functions_region: + type: string + cdp_enabled_contexts: + items: + type: string + type: array + hud_enabled: + type: boolean SitesSummary: description: SitesSummary model definition properties: diff --git a/internal/netlifyapi/model_admin_site.go b/internal/netlifyapi/model_admin_site.go index 8141d82..b48b351 100644 --- a/internal/netlifyapi/model_admin_site.go +++ b/internal/netlifyapi/model_admin_site.go @@ -57,6 +57,9 @@ type AdminSite struct { BuildTimelimit float32 `json:"build_timelimit"` DeployRetentionInDays float32 `json:"deploy_retention_in_days"` Labels []SiteLabel `json:"labels"` + FunctionsRegion *string `json:"functions_region,omitempty"` + CdpEnabledContexts []string `json:"cdp_enabled_contexts,omitempty"` + HudEnabled *bool `json:"hud_enabled,omitempty"` SiteBuildTimelimit float32 `json:"site_build_timelimit"` SiteBuildPreProcessTimeout float32 `json:"site_build_pre_process_timeout"` SiteFunctionsConfig map[string]interface{} `json:"site_functions_config"` @@ -986,6 +989,102 @@ func (o *AdminSite) SetLabels(v []SiteLabel) { o.Labels = v } +// GetFunctionsRegion returns the FunctionsRegion field value if set, zero value otherwise. +func (o *AdminSite) GetFunctionsRegion() string { + if o == nil || IsNil(o.FunctionsRegion) { + var ret string + return ret + } + return *o.FunctionsRegion +} + +// GetFunctionsRegionOk returns a tuple with the FunctionsRegion field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *AdminSite) GetFunctionsRegionOk() (*string, bool) { + if o == nil || IsNil(o.FunctionsRegion) { + return nil, false + } + return o.FunctionsRegion, true +} + +// HasFunctionsRegion returns a boolean if a field has been set. +func (o *AdminSite) HasFunctionsRegion() bool { + if o != nil && !IsNil(o.FunctionsRegion) { + return true + } + + return false +} + +// SetFunctionsRegion gets a reference to the given string and assigns it to the FunctionsRegion field. +func (o *AdminSite) SetFunctionsRegion(v string) { + o.FunctionsRegion = &v +} + +// GetCdpEnabledContexts returns the CdpEnabledContexts field value if set, zero value otherwise. +func (o *AdminSite) GetCdpEnabledContexts() []string { + if o == nil || IsNil(o.CdpEnabledContexts) { + var ret []string + return ret + } + return o.CdpEnabledContexts +} + +// GetCdpEnabledContextsOk returns a tuple with the CdpEnabledContexts field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *AdminSite) GetCdpEnabledContextsOk() ([]string, bool) { + if o == nil || IsNil(o.CdpEnabledContexts) { + return nil, false + } + return o.CdpEnabledContexts, true +} + +// HasCdpEnabledContexts returns a boolean if a field has been set. +func (o *AdminSite) HasCdpEnabledContexts() bool { + if o != nil && !IsNil(o.CdpEnabledContexts) { + return true + } + + return false +} + +// SetCdpEnabledContexts gets a reference to the given []string and assigns it to the CdpEnabledContexts field. +func (o *AdminSite) SetCdpEnabledContexts(v []string) { + o.CdpEnabledContexts = v +} + +// GetHudEnabled returns the HudEnabled field value if set, zero value otherwise. +func (o *AdminSite) GetHudEnabled() bool { + if o == nil || IsNil(o.HudEnabled) { + var ret bool + return ret + } + return *o.HudEnabled +} + +// GetHudEnabledOk returns a tuple with the HudEnabled field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *AdminSite) GetHudEnabledOk() (*bool, bool) { + if o == nil || IsNil(o.HudEnabled) { + return nil, false + } + return o.HudEnabled, true +} + +// HasHudEnabled returns a boolean if a field has been set. +func (o *AdminSite) HasHudEnabled() bool { + if o != nil && !IsNil(o.HudEnabled) { + return true + } + + return false +} + +// SetHudEnabled gets a reference to the given bool and assigns it to the HudEnabled field. +func (o *AdminSite) SetHudEnabled(v bool) { + o.HudEnabled = &v +} + // GetSiteBuildTimelimit returns the SiteBuildTimelimit field value func (o *AdminSite) GetSiteBuildTimelimit() float32 { if o == nil { @@ -1136,6 +1235,15 @@ func (o AdminSite) ToMap() (map[string]interface{}, error) { toSerialize["build_timelimit"] = o.BuildTimelimit toSerialize["deploy_retention_in_days"] = o.DeployRetentionInDays toSerialize["labels"] = o.Labels + if !IsNil(o.FunctionsRegion) { + toSerialize["functions_region"] = o.FunctionsRegion + } + if !IsNil(o.CdpEnabledContexts) { + toSerialize["cdp_enabled_contexts"] = o.CdpEnabledContexts + } + if !IsNil(o.HudEnabled) { + toSerialize["hud_enabled"] = o.HudEnabled + } toSerialize["site_build_timelimit"] = o.SiteBuildTimelimit toSerialize["site_build_pre_process_timeout"] = o.SiteBuildPreProcessTimeout toSerialize["site_functions_config"] = o.SiteFunctionsConfig @@ -1259,6 +1367,9 @@ func (o *AdminSite) UnmarshalJSON(data []byte) (err error) { delete(additionalProperties, "build_timelimit") delete(additionalProperties, "deploy_retention_in_days") delete(additionalProperties, "labels") + delete(additionalProperties, "functions_region") + delete(additionalProperties, "cdp_enabled_contexts") + delete(additionalProperties, "hud_enabled") delete(additionalProperties, "site_build_timelimit") delete(additionalProperties, "site_build_pre_process_timeout") delete(additionalProperties, "site_functions_config") diff --git a/internal/netlifyapi/model_partial_site.go b/internal/netlifyapi/model_partial_site.go index d5994ee..49daf2b 100644 --- a/internal/netlifyapi/model_partial_site.go +++ b/internal/netlifyapi/model_partial_site.go @@ -56,6 +56,9 @@ type PartialSite struct { BuildTimelimit *float32 `json:"build_timelimit,omitempty"` DeployRetentionInDays *float32 `json:"deploy_retention_in_days,omitempty"` Labels []SiteLabel `json:"labels,omitempty"` + FunctionsRegion *string `json:"functions_region,omitempty"` + CdpEnabledContexts []string `json:"cdp_enabled_contexts,omitempty"` + HudEnabled *bool `json:"hud_enabled,omitempty"` AdditionalProperties map[string]interface{} } @@ -1230,6 +1233,102 @@ func (o *PartialSite) SetLabels(v []SiteLabel) { o.Labels = v } +// GetFunctionsRegion returns the FunctionsRegion field value if set, zero value otherwise. +func (o *PartialSite) GetFunctionsRegion() string { + if o == nil || IsNil(o.FunctionsRegion) { + var ret string + return ret + } + return *o.FunctionsRegion +} + +// GetFunctionsRegionOk returns a tuple with the FunctionsRegion field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *PartialSite) GetFunctionsRegionOk() (*string, bool) { + if o == nil || IsNil(o.FunctionsRegion) { + return nil, false + } + return o.FunctionsRegion, true +} + +// HasFunctionsRegion returns a boolean if a field has been set. +func (o *PartialSite) HasFunctionsRegion() bool { + if o != nil && !IsNil(o.FunctionsRegion) { + return true + } + + return false +} + +// SetFunctionsRegion gets a reference to the given string and assigns it to the FunctionsRegion field. +func (o *PartialSite) SetFunctionsRegion(v string) { + o.FunctionsRegion = &v +} + +// GetCdpEnabledContexts returns the CdpEnabledContexts field value if set, zero value otherwise. +func (o *PartialSite) GetCdpEnabledContexts() []string { + if o == nil || IsNil(o.CdpEnabledContexts) { + var ret []string + return ret + } + return o.CdpEnabledContexts +} + +// GetCdpEnabledContextsOk returns a tuple with the CdpEnabledContexts field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *PartialSite) GetCdpEnabledContextsOk() ([]string, bool) { + if o == nil || IsNil(o.CdpEnabledContexts) { + return nil, false + } + return o.CdpEnabledContexts, true +} + +// HasCdpEnabledContexts returns a boolean if a field has been set. +func (o *PartialSite) HasCdpEnabledContexts() bool { + if o != nil && !IsNil(o.CdpEnabledContexts) { + return true + } + + return false +} + +// SetCdpEnabledContexts gets a reference to the given []string and assigns it to the CdpEnabledContexts field. +func (o *PartialSite) SetCdpEnabledContexts(v []string) { + o.CdpEnabledContexts = v +} + +// GetHudEnabled returns the HudEnabled field value if set, zero value otherwise. +func (o *PartialSite) GetHudEnabled() bool { + if o == nil || IsNil(o.HudEnabled) { + var ret bool + return ret + } + return *o.HudEnabled +} + +// GetHudEnabledOk returns a tuple with the HudEnabled field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *PartialSite) GetHudEnabledOk() (*bool, bool) { + if o == nil || IsNil(o.HudEnabled) { + return nil, false + } + return o.HudEnabled, true +} + +// HasHudEnabled returns a boolean if a field has been set. +func (o *PartialSite) HasHudEnabled() bool { + if o != nil && !IsNil(o.HudEnabled) { + return true + } + + return false +} + +// SetHudEnabled gets a reference to the given bool and assigns it to the HudEnabled field. +func (o *PartialSite) SetHudEnabled(v bool) { + o.HudEnabled = &v +} + func (o PartialSite) MarshalJSON() ([]byte, error) { toSerialize,err := o.ToMap() if err != nil { @@ -1348,6 +1447,15 @@ func (o PartialSite) ToMap() (map[string]interface{}, error) { if !IsNil(o.Labels) { toSerialize["labels"] = o.Labels } + if !IsNil(o.FunctionsRegion) { + toSerialize["functions_region"] = o.FunctionsRegion + } + if !IsNil(o.CdpEnabledContexts) { + toSerialize["cdp_enabled_contexts"] = o.CdpEnabledContexts + } + if !IsNil(o.HudEnabled) { + toSerialize["hud_enabled"] = o.HudEnabled + } for key, value := range o.AdditionalProperties { toSerialize[key] = value @@ -1406,6 +1514,9 @@ func (o *PartialSite) UnmarshalJSON(data []byte) (err error) { delete(additionalProperties, "build_timelimit") delete(additionalProperties, "deploy_retention_in_days") delete(additionalProperties, "labels") + delete(additionalProperties, "functions_region") + delete(additionalProperties, "cdp_enabled_contexts") + delete(additionalProperties, "hud_enabled") o.AdditionalProperties = additionalProperties } diff --git a/internal/netlifyapi/model_site.go b/internal/netlifyapi/model_site.go index 563fb7e..0eb6f32 100644 --- a/internal/netlifyapi/model_site.go +++ b/internal/netlifyapi/model_site.go @@ -57,6 +57,9 @@ type Site struct { BuildTimelimit float32 `json:"build_timelimit"` DeployRetentionInDays float32 `json:"deploy_retention_in_days"` Labels []SiteLabel `json:"labels"` + FunctionsRegion *string `json:"functions_region,omitempty"` + CdpEnabledContexts []string `json:"cdp_enabled_contexts,omitempty"` + HudEnabled *bool `json:"hud_enabled,omitempty"` AdditionalProperties map[string]interface{} } @@ -979,6 +982,102 @@ func (o *Site) SetLabels(v []SiteLabel) { o.Labels = v } +// GetFunctionsRegion returns the FunctionsRegion field value if set, zero value otherwise. +func (o *Site) GetFunctionsRegion() string { + if o == nil || IsNil(o.FunctionsRegion) { + var ret string + return ret + } + return *o.FunctionsRegion +} + +// GetFunctionsRegionOk returns a tuple with the FunctionsRegion field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Site) GetFunctionsRegionOk() (*string, bool) { + if o == nil || IsNil(o.FunctionsRegion) { + return nil, false + } + return o.FunctionsRegion, true +} + +// HasFunctionsRegion returns a boolean if a field has been set. +func (o *Site) HasFunctionsRegion() bool { + if o != nil && !IsNil(o.FunctionsRegion) { + return true + } + + return false +} + +// SetFunctionsRegion gets a reference to the given string and assigns it to the FunctionsRegion field. +func (o *Site) SetFunctionsRegion(v string) { + o.FunctionsRegion = &v +} + +// GetCdpEnabledContexts returns the CdpEnabledContexts field value if set, zero value otherwise. +func (o *Site) GetCdpEnabledContexts() []string { + if o == nil || IsNil(o.CdpEnabledContexts) { + var ret []string + return ret + } + return o.CdpEnabledContexts +} + +// GetCdpEnabledContextsOk returns a tuple with the CdpEnabledContexts field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Site) GetCdpEnabledContextsOk() ([]string, bool) { + if o == nil || IsNil(o.CdpEnabledContexts) { + return nil, false + } + return o.CdpEnabledContexts, true +} + +// HasCdpEnabledContexts returns a boolean if a field has been set. +func (o *Site) HasCdpEnabledContexts() bool { + if o != nil && !IsNil(o.CdpEnabledContexts) { + return true + } + + return false +} + +// SetCdpEnabledContexts gets a reference to the given []string and assigns it to the CdpEnabledContexts field. +func (o *Site) SetCdpEnabledContexts(v []string) { + o.CdpEnabledContexts = v +} + +// GetHudEnabled returns the HudEnabled field value if set, zero value otherwise. +func (o *Site) GetHudEnabled() bool { + if o == nil || IsNil(o.HudEnabled) { + var ret bool + return ret + } + return *o.HudEnabled +} + +// GetHudEnabledOk returns a tuple with the HudEnabled field value if set, nil otherwise +// and a boolean to check if the value has been set. +func (o *Site) GetHudEnabledOk() (*bool, bool) { + if o == nil || IsNil(o.HudEnabled) { + return nil, false + } + return o.HudEnabled, true +} + +// HasHudEnabled returns a boolean if a field has been set. +func (o *Site) HasHudEnabled() bool { + if o != nil && !IsNil(o.HudEnabled) { + return true + } + + return false +} + +// SetHudEnabled gets a reference to the given bool and assigns it to the HudEnabled field. +func (o *Site) SetHudEnabled(v bool) { + o.HudEnabled = &v +} + func (o Site) MarshalJSON() ([]byte, error) { toSerialize,err := o.ToMap() if err != nil { @@ -1025,6 +1124,15 @@ func (o Site) ToMap() (map[string]interface{}, error) { toSerialize["build_timelimit"] = o.BuildTimelimit toSerialize["deploy_retention_in_days"] = o.DeployRetentionInDays toSerialize["labels"] = o.Labels + if !IsNil(o.FunctionsRegion) { + toSerialize["functions_region"] = o.FunctionsRegion + } + if !IsNil(o.CdpEnabledContexts) { + toSerialize["cdp_enabled_contexts"] = o.CdpEnabledContexts + } + if !IsNil(o.HudEnabled) { + toSerialize["hud_enabled"] = o.HudEnabled + } for key, value := range o.AdditionalProperties { toSerialize[key] = value @@ -1139,6 +1247,9 @@ func (o *Site) UnmarshalJSON(data []byte) (err error) { delete(additionalProperties, "build_timelimit") delete(additionalProperties, "deploy_retention_in_days") delete(additionalProperties, "labels") + delete(additionalProperties, "functions_region") + delete(additionalProperties, "cdp_enabled_contexts") + delete(additionalProperties, "hud_enabled") o.AdditionalProperties = additionalProperties } diff --git a/internal/provider/netlify_validators/site_deploy_settings_deploy_branches_validator.go b/internal/provider/netlify_validators/site_deploy_settings_deploy_branches_validator.go new file mode 100644 index 0000000..c1d367e --- /dev/null +++ b/internal/provider/netlify_validators/site_deploy_settings_deploy_branches_validator.go @@ -0,0 +1,101 @@ +package netlify_validators + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ validator.List = SiteDeploySettingsDeployBranchesValidator{} +) + +type SiteDeploySettingsDeployBranchesValidator struct { + AllBranchesPathExpression path.Expression +} + +type SiteDeploySettingsDeployBranchesValidatorRequest struct { + Config tfsdk.Config + ConfigValue attr.Value + Path path.Path + PathExpression path.Expression +} + +type SiteDeploySettingsDeployBranchesValidatorResponse struct { + Diagnostics diag.Diagnostics +} + +func (av SiteDeploySettingsDeployBranchesValidator) Description(ctx context.Context) string { + return av.MarkdownDescription(ctx) +} + +func (av SiteDeploySettingsDeployBranchesValidator) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("Ensure that an attribute is a non-empty list only if %q is set to false", av.AllBranchesPathExpression) +} + +func (av SiteDeploySettingsDeployBranchesValidator) Validate(ctx context.Context, req SiteDeploySettingsDeployBranchesValidatorRequest, res *SiteDeploySettingsDeployBranchesValidatorResponse) { + // Delay validation until all involved attributes have a known value + if req.ConfigValue.IsUnknown() { + return + } + + emptyList, diags := types.ListValue(types.StringType, []attr.Value{}) + res.Diagnostics.Append(diags...) + if diags.HasError() { + return + } + + isNonEmpty := !req.ConfigValue.IsNull() && !req.ConfigValue.Equal(emptyList) + + matchedPaths, diags := req.Config.PathMatches(ctx, req.PathExpression.Merge(av.AllBranchesPathExpression)) + res.Diagnostics.Append(diags...) + if diags.HasError() { + return + } + + for _, mp := range matchedPaths { + var mpVal attr.Value + diags = req.Config.GetAttribute(ctx, mp, &mpVal) + res.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + // Delay validation until all involved attributes have a known value + if mpVal.IsUnknown() { + return + } + + isAllBranches := mpVal.Equal(types.BoolValue(true)) + + if isAllBranches && isNonEmpty { + res.Diagnostics.Append(validatordiag.InvalidAttributeCombinationDiagnostic( + req.Path, + fmt.Sprintf("Attribute %q must be an empty list if %q is set to true", req.Path, mp), + )) + } + } +} + +func (av SiteDeploySettingsDeployBranchesValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) { + validateReq := SiteDeploySettingsDeployBranchesValidatorRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &SiteDeploySettingsDeployBranchesValidatorResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 2b44107..0f8770f 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -140,6 +140,7 @@ func (p *NetlifyProvider) Resources(ctx context.Context) []func() resource.Resou NewEnvironmentVariableResource, NewLogDrainResource, NewSiteBuildSettingsResource, + NewSiteDeploySettingsResource, } } diff --git a/internal/provider/site_deploy_settings_resource.go b/internal/provider/site_deploy_settings_resource.go new file mode 100644 index 0000000..2d7db4d --- /dev/null +++ b/internal/provider/site_deploy_settings_resource.go @@ -0,0 +1,330 @@ +package provider + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "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/listdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "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/netlify/terraform-provider-netlify/internal/netlifyapi" + "github.com/netlify/terraform-provider-netlify/internal/provider/netlify_validators" +) + +var ( + _ resource.Resource = &siteDeploySettingsResource{} + _ resource.ResourceWithConfigure = &siteDeploySettingsResource{} + _ resource.ResourceWithImportState = &siteDeploySettingsResource{} +) + +func NewSiteDeploySettingsResource() resource.Resource { + return &siteDeploySettingsResource{} +} + +type siteDeploySettingsResource struct { + data NetlifyProviderData +} + +type siteDeploySettingsResourceModel struct { + SiteID types.String `tfsdk:"site_id"` + LastUpdated types.String `tfsdk:"last_updated"` + FunctionsRegion types.String `tfsdk:"functions_region"` + + ProductionBranch types.String `tfsdk:"production_branch"` + BranchDeployAllBranches types.Bool `tfsdk:"branch_deploy_all_branches"` + BranchDeployBranches []types.String `tfsdk:"branch_deploy_branches"` + DeployPreviews types.Bool `tfsdk:"deploy_previews"` + NetlifyDrawerInDeployPreviews types.Bool `tfsdk:"netlify_drawer_in_deploy_previews"` + NetlifyDrawerInBranchDeploys types.Bool `tfsdk:"netlify_drawer_in_branch_deploys"` + NetlifyHeadsUpDisplay types.Bool `tfsdk:"netlify_heads_up_display"` + + CustomDomain types.String `tfsdk:"custom_domain"` + DomainAliases []types.String `tfsdk:"domain_aliases"` + BranchDeployCustomDomain types.String `tfsdk:"branch_deploy_custom_domain"` + DeployPreviewCustomDomain types.String `tfsdk:"deploy_preview_custom_domain"` +} + +func (r *siteDeploySettingsResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_site_deploy_settings" +} + +func (r *siteDeploySettingsResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + data, ok := req.ProviderData.(NetlifyProviderData) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected NetlifyProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.data = data +} + +func (r *siteDeploySettingsResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + emptyList, diags := types.ListValue(types.StringType, []attr.Value{}) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "site_id": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "last_updated": schema.StringAttribute{ + Computed: true, + }, + "functions_region": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("us-east-2"), + }, + "production_branch": schema.StringAttribute{ + Required: true, + }, + "branch_deploy_all_branches": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "branch_deploy_branches": schema.ListAttribute{ + Optional: true, + Computed: true, + ElementType: types.StringType, + Default: listdefault.StaticValue(emptyList), + Validators: []validator.List{ + netlify_validators.SiteDeploySettingsDeployBranchesValidator{ + AllBranchesPathExpression: path.MatchRoot("branch_deploy_all_branches"), + }, + }, + }, + "deploy_previews": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "netlify_drawer_in_deploy_previews": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "netlify_drawer_in_branch_deploys": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "netlify_heads_up_display": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "custom_domain": schema.StringAttribute{ + Optional: true, + }, + "domain_aliases": schema.ListAttribute{ + Optional: true, + Computed: true, + ElementType: types.StringType, + Default: listdefault.StaticValue(emptyList), + }, + "branch_deploy_custom_domain": schema.StringAttribute{ + Optional: true, + }, + "deploy_preview_custom_domain": schema.StringAttribute{ + Optional: true, + }, + }, + } +} + +func (r *siteDeploySettingsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan siteDeploySettingsResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + r.write(ctx, &plan, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *siteDeploySettingsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state siteDeploySettingsResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + r.read(ctx, &state, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *siteDeploySettingsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan siteDeploySettingsResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + r.write(ctx, &plan, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *siteDeploySettingsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state siteDeploySettingsResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.AddWarning("Site deploy settings are now unmanaged.", "Site deploy settings are now unmanaged. The site will continue to deploy with the last settings.") +} + +func (r *siteDeploySettingsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("site_id"), req, resp) +} + +func (r *siteDeploySettingsResource) read(ctx context.Context, state *siteDeploySettingsResourceModel, diagnostics *diag.Diagnostics) { + site, _, err := r.data.client.SitesAPI.GetSite(ctx, state.SiteID.ValueString()).Execute() + if err != nil { + diagnostics.AddError( + "Error reading site build settings", + fmt.Sprintf("Could not read site build settings for site %q: %q", state.SiteID.ValueString(), err.Error()), + ) + return + } + + state.FunctionsRegion = types.StringPointerValue(site.FunctionsRegion) + state.ProductionBranch = types.StringPointerValue(site.BuildSettings.RepoBranch) + allowedBranchedLen := len(site.BuildSettings.AllowedBranches) + state.BranchDeployAllBranches = types.BoolValue(allowedBranchedLen == 0) + if allowedBranchedLen == 0 { + state.BranchDeployBranches = make([]types.String, 0) + } else { + state.BranchDeployBranches = make([]types.String, 0, allowedBranchedLen-1) + for _, branch := range site.BuildSettings.AllowedBranches { + if branch != *site.BuildSettings.RepoBranch { + state.BranchDeployBranches = append(state.BranchDeployBranches, types.StringValue(branch)) + } + } + } + if site.BuildSettings.SkipPrs == nil { + state.DeployPreviews = types.BoolValue(true) + } else { + state.DeployPreviews = types.BoolValue(!*site.BuildSettings.SkipPrs) + } + state.NetlifyDrawerInDeployPreviews = types.BoolValue(false) + state.NetlifyDrawerInBranchDeploys = types.BoolValue(false) + for _, context := range site.CdpEnabledContexts { + if context == "deploy-preview" { + state.NetlifyDrawerInDeployPreviews = types.BoolValue(true) + } else if context == "branch-deploy" { + state.NetlifyDrawerInBranchDeploys = types.BoolValue(true) + } + } + state.NetlifyHeadsUpDisplay = types.BoolPointerValue(site.HudEnabled) + state.CustomDomain = types.StringValue(site.CustomDomain) + state.DomainAliases = make([]types.String, len(site.DomainAliases)) + for i, domainAlias := range site.DomainAliases { + state.DomainAliases[i] = types.StringValue(domainAlias) + } + state.BranchDeployCustomDomain = types.StringValue(site.BranchDeployCustomDomain) + state.DeployPreviewCustomDomain = types.StringValue(site.DeployPreviewCustomDomain) +} + +func (r *siteDeploySettingsResource) write(ctx context.Context, plan *siteDeploySettingsResourceModel, diagnostics *diag.Diagnostics) { + var curState siteDeploySettingsResourceModel + curState.SiteID = plan.SiteID + r.read(ctx, &curState, diagnostics) + if diagnostics.HasError() { + return + } + + allowedBranches := make([]string, 0, len(plan.BranchDeployBranches)+1) + if !plan.BranchDeployAllBranches.ValueBool() { + allowedBranches = append(allowedBranches, plan.ProductionBranch.ValueString()) + for _, branch := range plan.BranchDeployBranches { + allowedBranches = append(allowedBranches, branch.ValueString()) + } + } + skipPrs := !plan.DeployPreviews.ValueBool() + cdpContexts := make([]string, 0, 2) + if plan.NetlifyDrawerInDeployPreviews.ValueBool() { + cdpContexts = append(cdpContexts, "deploy-preview") + } + if plan.NetlifyDrawerInBranchDeploys.ValueBool() { + cdpContexts = append(cdpContexts, "branch-deploy") + } + domainAliases := make([]string, len(plan.DomainAliases)) + for i, domainAlias := range plan.DomainAliases { + domainAliases[i] = domainAlias.ValueString() + } + site := netlifyapi.PartialSite{ + FunctionsRegion: plan.FunctionsRegion.ValueStringPointer(), + BuildSettings: &netlifyapi.Repo{ + RepoBranch: plan.ProductionBranch.ValueStringPointer(), + AllowedBranches: allowedBranches, + SkipPrs: &skipPrs, + }, + CdpEnabledContexts: cdpContexts, + HudEnabled: plan.NetlifyHeadsUpDisplay.ValueBoolPointer(), + CustomDomain: plan.CustomDomain.ValueStringPointer(), + DomainAliases: domainAliases, + BranchDeployCustomDomain: plan.BranchDeployCustomDomain.ValueStringPointer(), + DeployPreviewCustomDomain: plan.DeployPreviewCustomDomain.ValueStringPointer(), + } + + _, _, err := r.data.client.SitesAPI. + UpdateSite(ctx, plan.SiteID.ValueString()). + PartialSite(site). + Execute() + if err != nil { + diagnostics.AddError( + "Error updating site build settings", + fmt.Sprintf("Could not update site build settings for site %q: %q", plan.SiteID.ValueString(), err.Error()), + ) + return + } + + plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) +} diff --git a/openapi.json b/openapi.json index 84b6033..302be5e 100644 --- a/openapi.json +++ b/openapi.json @@ -13369,6 +13369,18 @@ "items": { "$ref": "#/components/schemas/SiteLabel" } + }, + "functions_region": { + "type": "string" + }, + "cdp_enabled_contexts": { + "type": "array", + "items": { + "type": "string" + } + }, + "hud_enabled": { + "type": "boolean" } }, "required": [ @@ -13542,6 +13554,18 @@ "items": { "$ref": "#/components/schemas/SiteLabel" } + }, + "functions_region": { + "type": "string" + }, + "cdp_enabled_contexts": { + "type": "array", + "items": { + "type": "string" + } + }, + "hud_enabled": { + "type": "boolean" } }, "description": "Site model definition"