diff --git a/docs/data-sources/site.md b/docs/data-sources/site.md index 3b58707..4c00102 100644 --- a/docs/data-sources/site.md +++ b/docs/data-sources/site.md @@ -31,7 +31,7 @@ data "netlify_site" "blog" { ### Optional - `name` (String) -- `team_slug` (String) +- `team_slug` (String) Required if name is specified and a default team was not configured in the provider configuration. ### Read-Only diff --git a/docs/data-sources/sites.md b/docs/data-sources/sites.md index a860de3..707646f 100644 --- a/docs/data-sources/sites.md +++ b/docs/data-sources/sites.md @@ -22,9 +22,9 @@ data "netlify_sites" "team" { ## Schema -### Required +### Optional -- `team_slug` (String) +- `team_slug` (String) Required if a default team was not configured in the provider configuration. ### Read-Only diff --git a/docs/data-sources/team.md b/docs/data-sources/team.md index 5736dd1..41fa319 100644 --- a/docs/data-sources/team.md +++ b/docs/data-sources/team.md @@ -29,9 +29,9 @@ data "netlify_team" "team" { ### Optional -- `slug` (String) +- `id` (String) ID or slug are required if a default team was not configured in the provider configuration. +- `slug` (String) ID or slug are required if a default team was not configured in the provider configuration. ### Read-Only -- `id` (String) The ID of this resource. - `name` (String) diff --git a/docs/index.md b/docs/index.md index 5f21b6f..ba6b668 100644 --- a/docs/index.md +++ b/docs/index.md @@ -48,19 +48,21 @@ terraform { provider "netlify" { token = var.netlify_api_token + # Optionally, set a default team through its ID or its slug to avoid repeating it. + default_team_slug = "your-team-slug" } data "netlify_team" "team" { - slug = "your-team-slug" + # slug coming from the default team } data "netlify_site" "blog" { - team_slug = data.netlify_team.team.slug - name = "blog" + # team_slug coming from the default team + name = "blog" } resource "netlify_environment_variable" "astro_database_file" { - team_id = data.netlify_team.team.id + # team_id coming from the default team site_id = data.netlify_site.blog.id key = "ASTRO_DATABASE_FILE" values = [ @@ -77,5 +79,7 @@ resource "netlify_environment_variable" "astro_database_file" { ### Optional +- `default_team_id` (String) The default team ID to use for resources that require a team ID or a team slug. Warning: Changing this value may not trigger recreation of resources. +- `default_team_slug` (String) The default team slug to use for resources that require a team ID or a team slug. Warning: Changing this value may not trigger recreation of resources. - `endpoint` (String) Defaults to: https://api.netlify.com - `token` (String, Sensitive) Read: https://docs.netlify.com/api/get-started/#authentication , will use the `NETLIFY_API_TOKEN` environment variable if not set. diff --git a/docs/resources/dns_zone.md b/docs/resources/dns_zone.md index 0aecbda..63827be 100644 --- a/docs/resources/dns_zone.md +++ b/docs/resources/dns_zone.md @@ -28,7 +28,10 @@ resource "netlify_dns_zone" "example" { ### Required - `name` (String) -- `team_slug` (String) + +### Optional + +- `team_slug` (String) Required if a default team was not configured in the provider configuration. ### Read-Only diff --git a/docs/resources/environment_variable.md b/docs/resources/environment_variable.md index 40e07d4..ce04a1b 100644 --- a/docs/resources/environment_variable.md +++ b/docs/resources/environment_variable.md @@ -91,13 +91,13 @@ resource "netlify_environment_variable" "astro_database_file" { ### Required - `key` (String) -- `team_id` (String) ### Optional - `scopes` (Set of String) One or more of builds, functions, runtime, and post-processing - `secret_values` (Attributes Set) (see [below for nested schema](#nestedatt--secret_values)) - `site_id` (String) +- `team_id` (String) Required if a default team was not configured in the provider configuration. - `values` (Attributes Set) (see [below for nested schema](#nestedatt--values)) ### Read-Only diff --git a/docs/resources/team_firewall_traffic_rules.md b/docs/resources/team_firewall_traffic_rules.md index 4d5425b..9933dd2 100644 --- a/docs/resources/team_firewall_traffic_rules.md +++ b/docs/resources/team_firewall_traffic_rules.md @@ -51,9 +51,12 @@ resource "netlify_team_firewall_traffic_rules" "team" { ### Required - `published` (Attributes) (see [below for nested schema](#nestedatt--published)) -- `team_id` (String) - `unpublished` (Attributes) (see [below for nested schema](#nestedatt--unpublished)) +### Optional + +- `team_id` (String) Required if a default team was not configured in the provider configuration. + ### Read-Only - `last_updated` (String) diff --git a/examples/misc/dns/main.tf b/examples/misc/dns/main.tf index 3da2ddc..9744d01 100644 --- a/examples/misc/dns/main.tf +++ b/examples/misc/dns/main.tf @@ -8,15 +8,12 @@ terraform { } # `token` comes from NETLIFY_API_TOKEN, but can be specified with a Terraform variable -provider "netlify" {} - -# data "netlify_team" "current" { -# slug = "ramon-test-1" -# } +provider "netlify" { + default_team_slug = "ramon-test-1" +} resource "netlify_dns_zone" "example" { - team_slug = "ramon-test-1" // data.netlify_team.current.slug - name = "example-tf-test-test.com" + name = "example-tf-test-test.com" lifecycle { prevent_destroy = true } diff --git a/examples/misc/env_vars/main.tf b/examples/misc/env_vars/main.tf index fbad800..6a5d857 100644 --- a/examples/misc/env_vars/main.tf +++ b/examples/misc/env_vars/main.tf @@ -8,7 +8,9 @@ terraform { } # `token` comes from NETLIFY_API_TOKEN, but can be specified with a Terraform variable -provider "netlify" {} +provider "netlify" { + default_team_slug = "ramon-test-1" +} data "netlify_team" "current" { slug = "ramon-test-1" @@ -31,6 +33,17 @@ resource "netlify_environment_variable" "woof" { ] } +resource "netlify_environment_variable" "woof2" { + site_id = data.netlify_site.platform_test.id + key = "WOOF2" + values = [ + { + value = "dogs are here", + context = "all", + } + ] +} + resource "netlify_environment_variable" "meow" { team_id = data.netlify_team.current.id site_id = data.netlify_site.platform_test.id diff --git a/examples/misc/site_data_sources/main.tf b/examples/misc/site_data_sources/main.tf index 61fb2bb..5d871a3 100644 --- a/examples/misc/site_data_sources/main.tf +++ b/examples/misc/site_data_sources/main.tf @@ -8,24 +8,25 @@ terraform { } # `token` comes from NETLIFY_API_TOKEN, but can be specified with a Terraform variable -provider "netlify" {} - -data "netlify_team" "current" { - slug = "ramon-test-1" +provider "netlify" { + default_team_slug = "ramon-test-1" } +data "netlify_team" "current" {} + data "netlify_site" "platform_test" { - team_slug = data.netlify_team.current.slug - name = "platform-test-1" + name = "platform-test-1" } -data "netlify_sites" "all" { +data "netlify_sites" "mine" {} + +data "netlify_sites" "testing" { team_slug = "netlify-testing" } output "sites" { value = [ - for site in data.netlify_sites.all.sites : site + for site in data.netlify_sites.testing.sites : site if site.custom_domain != "" ] } diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf index 69844c6..e144553 100644 --- a/examples/provider/provider.tf +++ b/examples/provider/provider.tf @@ -12,19 +12,21 @@ terraform { provider "netlify" { token = var.netlify_api_token + # Optionally, set a default team through its ID or its slug to avoid repeating it. + default_team_slug = "your-team-slug" } data "netlify_team" "team" { - slug = "your-team-slug" + # slug coming from the default team } data "netlify_site" "blog" { - team_slug = data.netlify_team.team.slug - name = "blog" + # team_slug coming from the default team + name = "blog" } resource "netlify_environment_variable" "astro_database_file" { - team_id = data.netlify_team.team.id + # team_id coming from the default team site_id = data.netlify_site.blog.id key = "ASTRO_DATABASE_FILE" values = [ diff --git a/internal/provider/dns_zone_resource.go b/internal/provider/dns_zone_resource.go index ace8605..71a14c2 100644 --- a/internal/provider/dns_zone_resource.go +++ b/internal/provider/dns_zone_resource.go @@ -10,6 +10,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/listplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/types" @@ -89,9 +90,12 @@ func (r *dnsZoneResource) Schema(_ context.Context, _ resource.SchemaRequest, re }, }, "team_slug": schema.StringAttribute{ - Required: true, + Computed: true, + Optional: true, + Description: "Required if a default team was not configured in the provider configuration.", PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), + stringplanmodifier.RequiresReplaceIfConfigured(), + stringplanmodifier.UseStateForUnknown(), }, }, "dns_servers": schema.ListAttribute{ @@ -102,7 +106,8 @@ func (r *dnsZoneResource) Schema(_ context.Context, _ resource.SchemaRequest, re }, }, "domain": schema.SingleNestedAttribute{ - Computed: true, + Computed: true, + PlanModifiers: []planmodifier.Object{objectplanmodifier.UseStateForUnknown()}, Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, @@ -138,10 +143,19 @@ func (r *dnsZoneResource) Create(ctx context.Context, req resource.CreateRequest return } + teamSlug := r.data.teamSlugOrDefault(plan.TeamSlug) + if teamSlug == nil { + resp.Diagnostics.AddError( + "Missing team slug", + "Team slug is required for creating a Netlify DNS zone. Please provide a team slug in the plan or configure a default team in the provider configuration.", + ) + return + } + dnsZone, _, err := r.data.client.DNSZonesAPI. CreateDnsZone(ctx). DnsZoneCreateParams(netlifyapi.DnsZoneCreateParams{ - AccountSlug: plan.TeamSlug.ValueStringPointer(), + AccountSlug: teamSlug, Name: plan.Name.ValueStringPointer(), }). Execute() @@ -151,7 +165,7 @@ func (r *dnsZoneResource) Create(ctx context.Context, req resource.CreateRequest fmt.Sprintf( "Could not create Netlify DNS zone %q (team slug: %q): %q", plan.Name.ValueString(), - plan.TeamSlug.ValueString(), + *teamSlug, err.Error(), ), ) @@ -160,6 +174,7 @@ func (r *dnsZoneResource) Create(ctx context.Context, req resource.CreateRequest plan.ID = types.StringValue(dnsZone.Id) plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC3339)) plan.TeamID = types.StringValue(dnsZone.AccountId) + plan.TeamSlug = types.StringValue(dnsZone.AccountSlug) dnsServers := make([]types.String, len(dnsZone.DnsServers)) for i, dnsServer := range dnsZone.DnsServers { dnsServers[i] = types.StringValue(dnsServer) @@ -249,7 +264,7 @@ func (r *dnsZoneResource) Read(ctx context.Context, req resource.ReadRequest, re } func (r *dnsZoneResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - resp.Diagnostics.AddError( + resp.Diagnostics.AddWarning( "Update not supported for Netlify DNS zones", "Update is not supported for Netlify DNS zones at this time.", ) diff --git a/internal/provider/environment_variable_resource.go b/internal/provider/environment_variable_resource.go index 505c737..061c8ff 100644 --- a/internal/provider/environment_variable_resource.go +++ b/internal/provider/environment_variable_resource.go @@ -83,9 +83,12 @@ func (r *environmentVariableResource) Schema(_ context.Context, _ resource.Schem Computed: true, }, "team_id": schema.StringAttribute{ - Required: true, + Computed: true, + Optional: true, + Description: "Required if a default team was not configured in the provider configuration.", PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), + stringplanmodifier.RequiresReplaceIfConfigured(), + stringplanmodifier.UseStateForUnknown(), }, }, "site_id": schema.StringAttribute{ @@ -188,6 +191,15 @@ func (r *environmentVariableResource) Create(ctx context.Context, req resource.C return } + teamId := r.data.teamIdOrDefault(plan.TeamID) + if teamId == nil { + resp.Diagnostics.AddError( + "Missing team ID", + "Team ID is required for creating a Netlify environment variable. Please provide a team ID in the plan or configure a default team in the provider configuration.", + ) + return + } + scopes := make([]string, len(plan.Scopes)) for i, scope := range plan.Scopes { scopes[i] = scope.ValueString() @@ -202,7 +214,7 @@ func (r *environmentVariableResource) Create(ctx context.Context, req resource.C isSecret = false } createEnvVars := r.data.client.EnvironmentVariablesAPI. - CreateEnvVars(ctx, plan.TeamID.ValueString()). + CreateEnvVars(ctx, *teamId). EnvVar([]netlifyapi.EnvVar{ { Key: plan.Key.ValueString(), @@ -221,7 +233,7 @@ func (r *environmentVariableResource) Create(ctx context.Context, req resource.C fmt.Sprintf( "Could not create Netlify environment variable order ID %q (team ID: %q, site ID: %q, secret: %v): %q", plan.Key.ValueString(), - plan.TeamID.ValueString(), + *teamId, plan.SiteID.ValueString(), isSecret, err.Error(), @@ -230,6 +242,7 @@ func (r *environmentVariableResource) Create(ctx context.Context, req resource.C return } plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC3339)) + plan.TeamID = types.StringValue(*teamId) resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) if resp.Diagnostics.HasError() { @@ -283,6 +296,11 @@ func (r *environmentVariableResource) Update(ctx context.Context, req resource.U if resp.Diagnostics.HasError() { return } + var state environmentVariableResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } scopes := make([]string, len(plan.Scopes)) for i, scope := range plan.Scopes { @@ -298,7 +316,7 @@ func (r *environmentVariableResource) Update(ctx context.Context, req resource.U isSecret = false } updateEnvVar := r.data.client.EnvironmentVariablesAPI. - UpdateEnvVar(ctx, plan.TeamID.ValueString(), plan.Key.ValueString()). + UpdateEnvVar(ctx, state.TeamID.ValueString(), plan.Key.ValueString()). Key(plan.Key.ValueString()).UpdateEnvVarRequest(netlifyapi.UpdateEnvVarRequest{ Scopes: scopes, Values: values, @@ -314,7 +332,7 @@ func (r *environmentVariableResource) Update(ctx context.Context, req resource.U fmt.Sprintf( "Could not update Netlify environment variable order ID %q (team ID: %q, site ID: %q, secret: %v): %q", plan.Key.ValueString(), - plan.TeamID.ValueString(), + state.TeamID.ValueString(), plan.SiteID.ValueString(), isSecret, err.Error(), diff --git a/internal/provider/firewall_traffic_rules_resource.go b/internal/provider/firewall_traffic_rules_resource.go index 5bb8352..2ffdc77 100644 --- a/internal/provider/firewall_traffic_rules_resource.go +++ b/internal/provider/firewall_traffic_rules_resource.go @@ -12,6 +12,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/planmodifier" + "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" @@ -160,15 +161,33 @@ func (r *firewallTrafficRulesResource) Schema(_ context.Context, _ resource.Sche } var ( - description string - mdDescription string + description string + mdDescription string + teamIdDescription string + siteIdPlanModifiers []planmodifier.String + teamIdPlanModifiers []planmodifier.String ) if r.teamLevel { description = "Netlify team-level firewall traffic rules" mdDescription = "Netlify team-level firewall traffic rules. [Read more](https://docs.netlify.com/security/secure-access-to-sites/traffic-rules/)" + teamIdDescription = "Required if a default team was not configured in the provider configuration." + siteIdPlanModifiers = []planmodifier.String{ + netlify_planmodifiers.UseNullForUnknown(), + } + teamIdPlanModifiers = []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + stringplanmodifier.UseStateForUnknown(), + } } else { description = "Netlify site-level firewall traffic rules" mdDescription = "Netlify site-level firewall traffic rules. [Read more](https://docs.netlify.com/security/secure-access-to-sites/traffic-rules/)" + teamIdDescription = "" + siteIdPlanModifiers = []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + } + teamIdPlanModifiers = []planmodifier.String{ + netlify_planmodifiers.UseNullForUnknown(), + } } resp.Schema = schema.Schema{ @@ -176,18 +195,15 @@ func (r *firewallTrafficRulesResource) Schema(_ context.Context, _ resource.Sche MarkdownDescription: mdDescription, Attributes: map[string]schema.Attribute{ "site_id": schema.StringAttribute{ - Required: !r.teamLevel, - Computed: r.teamLevel, - PlanModifiers: []planmodifier.String{ - netlify_planmodifiers.UseNullForUnknown(), - }, + Required: !r.teamLevel, + Computed: r.teamLevel, + PlanModifiers: siteIdPlanModifiers, }, "team_id": schema.StringAttribute{ - Required: r.teamLevel, - Computed: !r.teamLevel, - PlanModifiers: []planmodifier.String{ - netlify_planmodifiers.UseNullForUnknown(), - }, + Optional: r.teamLevel, + Computed: true, + Description: teamIdDescription, + PlanModifiers: teamIdPlanModifiers, }, "last_updated": schema.StringAttribute{ Computed: true, @@ -347,8 +363,17 @@ func (r *firewallTrafficRulesResource) write(ctx context.Context, plan *firewall createSiteFirewallConfig.Unpublished = &unpublished if r.teamLevel { + teamId := r.data.teamIdOrDefault(plan.TeamID) + if teamId == nil { + diagnostics.AddError( + "Missing team ID", + "Team ID is required for managin Netlify team-level firewall traffic rules. Please provide a team ID in the plan or configure a default team in the provider configuration.", + ) + return + } + _, err := r.data.client.AccountsAPI. - UpdateAccountFirewallRuleSet(ctx, plan.TeamID.ValueString()). + UpdateAccountFirewallRuleSet(ctx, *teamId). CreateSiteFirewallConfig(createSiteFirewallConfig). Execute() if err != nil { @@ -356,13 +381,14 @@ func (r *firewallTrafficRulesResource) write(ctx context.Context, plan *firewall "Error updating team firewall rule set", fmt.Sprintf( "Could not update team firewall rule set %q: %q", - plan.TeamID.ValueString(), + *teamId, err.Error(), ), ) return } plan.SiteID = types.StringNull() + plan.TeamID = types.StringValue(*teamId) } else { _, err := r.data.client.SitesAPI. UpdateSiteFirewallRuleSet(ctx, plan.SiteID.ValueString()). diff --git a/internal/provider/provider.go b/internal/provider/provider.go index d87f8d3..4a708d6 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -2,14 +2,17 @@ package provider import ( "context" + "fmt" "net/url" "os" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/netlify/terraform-provider-netlify/internal/netlifyapi" ) @@ -21,12 +24,30 @@ type NetlifyProvider struct { } type NetlifyProviderModel struct { - Endpoint types.String `tfsdk:"endpoint"` - Token types.String `tfsdk:"token"` + Endpoint types.String `tfsdk:"endpoint"` + Token types.String `tfsdk:"token"` + DefaultTeamID types.String `tfsdk:"default_team_id"` + DefaultTeamSlug types.String `tfsdk:"default_team_slug"` } type NetlifyProviderData struct { - client *netlifyapi.APIClient + client *netlifyapi.APIClient + defaultTeamId *string + defaultTeamSlug *string +} + +func (d *NetlifyProviderData) teamIdOrDefault(value types.String) *string { + if value.IsUnknown() || value.IsNull() { + return d.defaultTeamId + } + return value.ValueStringPointer() +} + +func (d *NetlifyProviderData) teamSlugOrDefault(value types.String) *string { + if value.IsUnknown() || value.IsNull() { + return d.defaultTeamSlug + } + return value.ValueStringPointer() } func (p *NetlifyProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { @@ -46,6 +67,16 @@ func (p *NetlifyProvider) Schema(ctx context.Context, req provider.SchemaRequest Optional: true, Sensitive: true, }, + "default_team_id": schema.StringAttribute{ + MarkdownDescription: "The default team ID to use for resources that require a team ID or a team slug. Warning: Changing this value may not trigger recreation of resources.", + Optional: true, + Validators: []validator.String{stringvalidator.ConflictsWith(path.MatchRoot("default_team_slug"))}, + }, + "default_team_slug": schema.StringAttribute{ + MarkdownDescription: "The default team slug to use for resources that require a team ID or a team slug. Warning: Changing this value may not trigger recreation of resources.", + Optional: true, + Validators: []validator.String{stringvalidator.ConflictsWith(path.MatchRoot("default_team_id"))}, + }, }, } } @@ -129,6 +160,43 @@ func (p *NetlifyProvider) Configure(ctx context.Context, req provider.ConfigureR // TODO: Add debug/trace logging to the API client, perhaps by overriding the behavior of apiConfig.Debug data.client = netlifyapi.NewAPIClient(apiConfig) + var account *netlifyapi.Account + + if !config.DefaultTeamID.IsNull() { + var err error + account, _, err = data.client.AccountsAPI.GetAccount(ctx, config.DefaultTeamID.ValueString()).Execute() + if err != nil { + resp.Diagnostics.AddError( + "Error reading Netlify team", + fmt.Sprintf("Could not read Netlify team ID %q: %q", config.DefaultTeamID.ValueString(), err.Error()), + ) + return + } + } else if !config.DefaultTeamSlug.IsNull() { + accounts, _, err := data.client.AccountsAPI.ListAccountsForUser(ctx).Execute() + if err != nil { + resp.Diagnostics.AddError("Error reading Netlify team", fmt.Sprintf("Could not list Netlify teams: %q", err.Error())) + return + } + slugString := config.DefaultTeamSlug.ValueString() + for _, a := range accounts { + if a.Slug == slugString { + acc := a + account = &acc + break + } + } + if account == nil { + resp.Diagnostics.AddError("Error reading Netlify team", fmt.Sprintf("Could not find Netlify team with slug %q", slugString)) + return + } + } + + if account != nil { + data.defaultTeamId = &account.Id + data.defaultTeamSlug = &account.Slug + } + resp.DataSourceData = data resp.ResourceData = data } diff --git a/internal/provider/site_data_source.go b/internal/provider/site_data_source.go index 89f4aeb..c11786d 100644 --- a/internal/provider/site_data_source.go +++ b/internal/provider/site_data_source.go @@ -67,15 +67,13 @@ func (d *siteDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r }, }, "team_slug": schema.StringAttribute{ - Optional: true, - Computed: true, + Optional: true, + Computed: true, + Description: "Required if name is specified and a default team was not configured in the provider configuration.", }, "name": schema.StringAttribute{ Optional: true, Computed: true, - Validators: []validator.String{ - stringvalidator.AlsoRequires(path.MatchRoot("team_slug")), - }, }, "custom_domain": schema.StringAttribute{ Computed: true, @@ -108,12 +106,20 @@ func (d *siteDataSource) Read(ctx context.Context, req datasource.ReadRequest, r return } } else { + teamSlug := d.data.teamSlugOrDefault(config.TeamSlug) + if teamSlug == nil { + resp.Diagnostics.AddError( + "Missing team slug", + "Team slug is required for reading a Netlify site by its name. Please provide a team slug in the plan or configure a default team in the provider configuration.", + ) + return + } sites, _, err := d.data.client.SitesAPI. - ListSitesForAccount(ctx, config.TeamSlug.ValueString()). + ListSitesForAccount(ctx, *teamSlug). Name(config.Name.ValueString()). Execute() if err != nil { - resp.Diagnostics.AddError("Error reading Netlify team", fmt.Sprintf("Could not list Netlify sites in team %q: %q", config.TeamSlug.ValueString(), err.Error())) + resp.Diagnostics.AddError("Error reading Netlify team", fmt.Sprintf("Could not list Netlify sites in team %q: %q", *teamSlug, err.Error())) return } nameString := config.Name.ValueString() @@ -125,7 +131,7 @@ func (d *siteDataSource) Read(ctx context.Context, req datasource.ReadRequest, r } } if site == nil { - resp.Diagnostics.AddError("Error reading Netlify team", fmt.Sprintf("Could not find Netlify site with name %q in team %q", nameString, config.TeamSlug.ValueString())) + resp.Diagnostics.AddError("Error reading Netlify team", fmt.Sprintf("Could not find Netlify site with name %q in team %q", nameString, *teamSlug)) return } } diff --git a/internal/provider/sites_data_source.go b/internal/provider/sites_data_source.go index 4303a9a..86a53d3 100644 --- a/internal/provider/sites_data_source.go +++ b/internal/provider/sites_data_source.go @@ -61,7 +61,9 @@ func (d *sitesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "team_slug": schema.StringAttribute{ - Required: true, + Optional: true, + Computed: true, + Description: "Required if a default team was not configured in the provider configuration.", }, "sites": schema.ListNestedAttribute{ Computed: true, @@ -97,15 +99,24 @@ func (d *sitesDataSource) Read(ctx context.Context, req datasource.ReadRequest, return } + teamSlug := d.data.teamSlugOrDefault(config.TeamSlug) + if teamSlug == nil { + resp.Diagnostics.AddError( + "Missing team slug", + "Team slug is required for reading Netlify sites. Please provide a team slug in the plan or configure a default team in the provider configuration.", + ) + return + } + r := d.data.client.SitesAPI. - ListSitesForAccount(ctx, config.TeamSlug.ValueString()). + ListSitesForAccount(ctx, *teamSlug). PerPage(100) sites := make([]netlifyapi.Site, 0) var page int64 = 1 for { items, _, err := r.Page(page).Execute() if err != nil { - resp.Diagnostics.AddError("Error reading Netlify team", fmt.Sprintf("Could not list Netlify sites in team %q: %q", config.TeamSlug.ValueString(), err.Error())) + resp.Diagnostics.AddError("Error reading Netlify team", fmt.Sprintf("Could not list Netlify sites in team %q: %q", *teamSlug, err.Error())) return } if len(items) == 0 { diff --git a/internal/provider/team_data_source.go b/internal/provider/team_data_source.go index 713c5d5..6449005 100644 --- a/internal/provider/team_data_source.go +++ b/internal/provider/team_data_source.go @@ -4,11 +4,8 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/netlify/terraform-provider-netlify/internal/netlifyapi" ) @@ -57,15 +54,14 @@ func (d *teamDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, r resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ - Optional: true, - Computed: true, - Validators: []validator.String{ - stringvalidator.AtLeastOneOf(path.MatchRoot("slug")), - }, + Optional: true, + Computed: true, + Description: "ID or slug are required if a default team was not configured in the provider configuration.", }, "slug": schema.StringAttribute{ - Optional: true, - Computed: true, + Optional: true, + Computed: true, + Description: "ID or slug are required if a default team was not configured in the provider configuration.", }, "name": schema.StringAttribute{ Computed: true, @@ -82,15 +78,7 @@ func (d *teamDataSource) Read(ctx context.Context, req datasource.ReadRequest, r } var account *netlifyapi.Account - if !config.ID.IsUnknown() && !config.ID.IsNull() { - var err error - account, _, err = d.data.client.AccountsAPI.GetAccount(ctx, config.ID.ValueString()).Execute() - if err != nil { - resp.Diagnostics.AddError("Error reading Netlify team", fmt.Sprintf("Could not read Netlify team ID %q: %q", - config.ID.ValueString(), err.Error())) - return - } - } else { + if !config.Slug.IsUnknown() && !config.Slug.IsNull() { accounts, _, err := d.data.client.AccountsAPI.ListAccountsForUser(ctx).Execute() if err != nil { resp.Diagnostics.AddError("Error reading Netlify team", fmt.Sprintf("Could not list Netlify teams: %q", err.Error())) @@ -108,6 +96,23 @@ func (d *teamDataSource) Read(ctx context.Context, req datasource.ReadRequest, r resp.Diagnostics.AddError("Error reading Netlify team", fmt.Sprintf("Could not find Netlify team with slug %q", slugString)) return } + } else { + teamId := d.data.teamIdOrDefault(config.ID) + if teamId == nil { + resp.Diagnostics.AddError( + "Missing team information", + "Team information is required for reading a Netlify team. Please provide a team ID or slug in the plan or configure a default team in the provider configuration.", + ) + return + } + + var err error + account, _, err = d.data.client.AccountsAPI.GetAccount(ctx, *teamId).Execute() + if err != nil { + resp.Diagnostics.AddError("Error reading Netlify team", fmt.Sprintf("Could not read Netlify team ID %q: %q", + *teamId, err.Error())) + return + } } config.ID = types.StringValue(account.Id)