From 97bc6d4d98f2c200be3e81e794e066218e607b25 Mon Sep 17 00:00:00 2001 From: Ramon Snir Date: Mon, 13 May 2024 18:41:15 -0400 Subject: [PATCH] unify all DNS record resources --- docs/resources/dns_aaaa_record.md | 34 ------ docs/resources/dns_alias_record.md | 34 ------ docs/resources/dns_caa_record.md | 34 ------ docs/resources/dns_cname_record.md | 34 ------ docs/resources/dns_mx_record.md | 34 ------ docs/resources/dns_ns_record.md | 34 ------ .../{dns_a_record.md => dns_record.md} | 11 +- docs/resources/dns_spf_record.md | 34 ------ docs/resources/dns_txt_record.md | 34 ------ examples/dns/main.tf | 20 ++-- internal/provider/dns_record_resource.go | 88 ++++++++------- .../netlify_validators/required_if_equals.go | 105 ++++++++++++++++++ internal/provider/provider.go | 10 +- 13 files changed, 173 insertions(+), 333 deletions(-) delete mode 100644 docs/resources/dns_aaaa_record.md delete mode 100644 docs/resources/dns_alias_record.md delete mode 100644 docs/resources/dns_caa_record.md delete mode 100644 docs/resources/dns_cname_record.md delete mode 100644 docs/resources/dns_mx_record.md delete mode 100644 docs/resources/dns_ns_record.md rename docs/resources/{dns_a_record.md => dns_record.md} (80%) delete mode 100644 docs/resources/dns_spf_record.md delete mode 100644 docs/resources/dns_txt_record.md create mode 100644 internal/provider/netlify_validators/required_if_equals.go diff --git a/docs/resources/dns_aaaa_record.md b/docs/resources/dns_aaaa_record.md deleted file mode 100644 index 3c932c7..0000000 --- a/docs/resources/dns_aaaa_record.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "netlify_dns_aaaa_record Resource - netlify" -subcategory: "" -description: |- - ---- - -# netlify_dns_aaaa_record (Resource) - - - - - - -## Schema - -### Required - -- `hostname` (String) -- `value` (String) -- `zone_id` (String) - -### Optional - -- `ttl` (Number) - -### Read-Only - -- `flag` (Number) -- `id` (String) The ID of this resource. -- `last_updated` (String) -- `priority` (Number) -- `tag` (String) diff --git a/docs/resources/dns_alias_record.md b/docs/resources/dns_alias_record.md deleted file mode 100644 index 634bafc..0000000 --- a/docs/resources/dns_alias_record.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "netlify_dns_alias_record Resource - netlify" -subcategory: "" -description: |- - ---- - -# netlify_dns_alias_record (Resource) - - - - - - -## Schema - -### Required - -- `hostname` (String) -- `value` (String) -- `zone_id` (String) - -### Optional - -- `ttl` (Number) - -### Read-Only - -- `flag` (Number) -- `id` (String) The ID of this resource. -- `last_updated` (String) -- `priority` (Number) -- `tag` (String) diff --git a/docs/resources/dns_caa_record.md b/docs/resources/dns_caa_record.md deleted file mode 100644 index e3f43b4..0000000 --- a/docs/resources/dns_caa_record.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "netlify_dns_caa_record Resource - netlify" -subcategory: "" -description: |- - ---- - -# netlify_dns_caa_record (Resource) - - - - - - -## Schema - -### Required - -- `flag` (Number) -- `hostname` (String) -- `tag` (String) -- `value` (String) -- `zone_id` (String) - -### Optional - -- `ttl` (Number) - -### Read-Only - -- `id` (String) The ID of this resource. -- `last_updated` (String) -- `priority` (Number) diff --git a/docs/resources/dns_cname_record.md b/docs/resources/dns_cname_record.md deleted file mode 100644 index 210d3fa..0000000 --- a/docs/resources/dns_cname_record.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "netlify_dns_cname_record Resource - netlify" -subcategory: "" -description: |- - ---- - -# netlify_dns_cname_record (Resource) - - - - - - -## Schema - -### Required - -- `hostname` (String) -- `value` (String) -- `zone_id` (String) - -### Optional - -- `ttl` (Number) - -### Read-Only - -- `flag` (Number) -- `id` (String) The ID of this resource. -- `last_updated` (String) -- `priority` (Number) -- `tag` (String) diff --git a/docs/resources/dns_mx_record.md b/docs/resources/dns_mx_record.md deleted file mode 100644 index 030c8d2..0000000 --- a/docs/resources/dns_mx_record.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "netlify_dns_mx_record Resource - netlify" -subcategory: "" -description: |- - ---- - -# netlify_dns_mx_record (Resource) - - - - - - -## Schema - -### Required - -- `hostname` (String) -- `priority` (Number) -- `value` (String) -- `zone_id` (String) - -### Optional - -- `ttl` (Number) - -### Read-Only - -- `flag` (Number) -- `id` (String) The ID of this resource. -- `last_updated` (String) -- `tag` (String) diff --git a/docs/resources/dns_ns_record.md b/docs/resources/dns_ns_record.md deleted file mode 100644 index b9d836b..0000000 --- a/docs/resources/dns_ns_record.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "netlify_dns_ns_record Resource - netlify" -subcategory: "" -description: |- - ---- - -# netlify_dns_ns_record (Resource) - - - - - - -## Schema - -### Required - -- `hostname` (String) -- `value` (String) -- `zone_id` (String) - -### Optional - -- `ttl` (Number) - -### Read-Only - -- `flag` (Number) -- `id` (String) The ID of this resource. -- `last_updated` (String) -- `priority` (Number) -- `tag` (String) diff --git a/docs/resources/dns_a_record.md b/docs/resources/dns_record.md similarity index 80% rename from docs/resources/dns_a_record.md rename to docs/resources/dns_record.md index 770f606..840e3f7 100644 --- a/docs/resources/dns_a_record.md +++ b/docs/resources/dns_record.md @@ -1,12 +1,12 @@ --- # generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "netlify_dns_a_record Resource - netlify" +page_title: "netlify_dns_record Resource - netlify" subcategory: "" description: |- --- -# netlify_dns_a_record (Resource) +# netlify_dns_record (Resource) @@ -18,17 +18,18 @@ description: |- ### Required - `hostname` (String) +- `type` (String) - `value` (String) - `zone_id` (String) ### Optional +- `flag` (Number) +- `priority` (Number) +- `tag` (String) - `ttl` (Number) ### Read-Only -- `flag` (Number) - `id` (String) The ID of this resource. - `last_updated` (String) -- `priority` (Number) -- `tag` (String) diff --git a/docs/resources/dns_spf_record.md b/docs/resources/dns_spf_record.md deleted file mode 100644 index fa4ae14..0000000 --- a/docs/resources/dns_spf_record.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "netlify_dns_spf_record Resource - netlify" -subcategory: "" -description: |- - ---- - -# netlify_dns_spf_record (Resource) - - - - - - -## Schema - -### Required - -- `hostname` (String) -- `value` (String) -- `zone_id` (String) - -### Optional - -- `ttl` (Number) - -### Read-Only - -- `flag` (Number) -- `id` (String) The ID of this resource. -- `last_updated` (String) -- `priority` (Number) -- `tag` (String) diff --git a/docs/resources/dns_txt_record.md b/docs/resources/dns_txt_record.md deleted file mode 100644 index ca9102c..0000000 --- a/docs/resources/dns_txt_record.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "netlify_dns_txt_record Resource - netlify" -subcategory: "" -description: |- - ---- - -# netlify_dns_txt_record (Resource) - - - - - - -## Schema - -### Required - -- `hostname` (String) -- `value` (String) -- `zone_id` (String) - -### Optional - -- `ttl` (Number) - -### Read-Only - -- `flag` (Number) -- `id` (String) The ID of this resource. -- `last_updated` (String) -- `priority` (Number) -- `tag` (String) diff --git a/examples/dns/main.tf b/examples/dns/main.tf index bc82d0a..aac39aa 100644 --- a/examples/dns/main.tf +++ b/examples/dns/main.tf @@ -22,26 +22,30 @@ resource "netlify_dns_zone" "example" { } } -resource "netlify_dns_a_record" "cat" { +resource "netlify_dns_record" "cat" { + type = "A" zone_id = netlify_dns_zone.example.id hostname = "cat.example-tf-test-test.com" value = "10.0.0.15" } -resource "netlify_dns_cname_record" "dog" { +resource "netlify_dns_record" "dog" { + type = "CNAME" zone_id = netlify_dns_zone.example.id hostname = "dog.example-tf-test-test.com" value = "cat.example-tf-test-test.com" ttl = 60 } -resource "netlify_dns_txt_record" "bird" { +resource "netlify_dns_record" "bird" { + type = "TXT" zone_id = netlify_dns_zone.example.id hostname = "bird.example-tf-test-test.com" value = "hello world" } -resource "netlify_dns_mx_record" "fish" { +resource "netlify_dns_record" "fish" { + type = "MX" zone_id = netlify_dns_zone.example.id hostname = "fish.example-tf-test-test.com" value = "mail.example-tf-test-test.com" @@ -51,10 +55,10 @@ resource "netlify_dns_mx_record" "fish" { data "netlify_dns_zone" "example" { name = "example-tf-test-test.com" depends_on = [ - netlify_dns_a_record.cat, - netlify_dns_cname_record.dog, - netlify_dns_txt_record.bird, - netlify_dns_mx_record.fish, + netlify_dns_record.cat, + netlify_dns_record.dog, + netlify_dns_record.bird, + netlify_dns_record.fish, ] } diff --git a/internal/provider/dns_record_resource.go b/internal/provider/dns_record_resource.go index b9ad44f..6b6b771 100644 --- a/internal/provider/dns_record_resource.go +++ b/internal/provider/dns_record_resource.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -13,9 +14,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" "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/models" "github.com/netlify/terraform-provider-netlify/internal/plumbing/operations" + "github.com/netlify/terraform-provider-netlify/internal/provider/netlify_validators" ) var ( @@ -24,23 +27,19 @@ var ( _ resource.ResourceWithImportState = &dnsRecordResource{} ) -var NewDnsRecordResource = func(recordType string) func() resource.Resource { - return func() resource.Resource { - return &dnsRecordResource{ - recordType: recordType, - } - } +func NewDnsRecordResource() resource.Resource { + return &dnsRecordResource{} } type dnsRecordResource struct { - data NetlifyProviderData - recordType string + data NetlifyProviderData } type dnsRecordResourceModel struct { ZoneID types.String `tfsdk:"zone_id"` ID types.String `tfsdk:"id"` LastUpdated types.String `tfsdk:"last_updated"` + Type types.String `tfsdk:"type"` Hostname types.String `tfsdk:"hostname"` Value types.String `tfsdk:"value"` TTL types.Int64 `tfsdk:"ttl"` @@ -50,7 +49,7 @@ type dnsRecordResourceModel struct { } func (r *dnsRecordResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = fmt.Sprintf("%s_dns_%s_record", req.ProviderTypeName, strings.ToLower(r.recordType)) + resp.TypeName = req.ProviderTypeName + "_dns_record" } func (r *dnsRecordResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { @@ -89,6 +88,24 @@ func (r *dnsRecordResource) Schema(_ context.Context, _ resource.SchemaRequest, "last_updated": schema.StringAttribute{ Computed: true, }, + "type": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf( + "A", + "AAAA", + "ALIAS", + "CAA", + "CNAME", + "MX", + "NS", + "SPF", + "TXT", + ), + netlify_validators.RequiredIfEquals("CAA", path.MatchRoot("flag"), path.MatchRoot("tag")), + netlify_validators.RequiredIfEquals("MX", path.MatchRoot("priority")), + }, + }, "hostname": schema.StringAttribute{ Required: true, PlanModifiers: []planmodifier.String{ @@ -110,38 +127,28 @@ func (r *dnsRecordResource) Schema(_ context.Context, _ resource.SchemaRequest, }, }, "flag": schema.Int64Attribute{ + Optional: true, Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, }, "tag": schema.StringAttribute{ + Optional: true, Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, }, "priority": schema.Int64Attribute{ + Optional: true, Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, }, }, } - if r.recordType == "CAA" { - resp.Schema.Attributes["flag"] = schema.Int64Attribute{ - Required: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - } - resp.Schema.Attributes["tag"] = schema.StringAttribute{ - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - } - } - if r.recordType == "MX" { - resp.Schema.Attributes["priority"] = schema.Int64Attribute{ - Required: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - } - } } func (r *dnsRecordResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -151,17 +158,18 @@ func (r *dnsRecordResource) Create(ctx context.Context, req resource.CreateReque return } + recordType := plan.Type.ValueString() dnsRecordCreate := models.DNSRecordCreate{ - Type: r.recordType, + Type: recordType, Hostname: plan.Hostname.ValueString(), Value: plan.Value.ValueString(), TTL: plan.TTL.ValueInt64(), } - if r.recordType == "CAA" { + if recordType == "CAA" { dnsRecordCreate.Flag = plan.Flag.ValueInt64() dnsRecordCreate.Tag = plan.Tag.ValueString() } - if r.recordType == "MX" { + if recordType == "MX" { dnsRecordCreate.Priority = plan.Priority.ValueInt64() } dnsRecord, err := r.data.client.Operations.CreateDNSRecord( @@ -185,11 +193,11 @@ func (r *dnsRecordResource) Create(ctx context.Context, req resource.CreateReque } plan.ID = types.StringValue(dnsRecord.Payload.ID) plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) - if r.recordType != "CAA" { + if recordType != "CAA" { plan.Flag = types.Int64Null() plan.Tag = types.StringNull() } - if r.recordType != "MX" { + if recordType != "MX" { plan.Priority = types.Int64Null() } @@ -212,7 +220,7 @@ func (r *dnsRecordResource) Read(ctx context.Context, req resource.ReadRequest, WithDNSRecordID(state.ID.ValueString()), r.data.authInfo, ) - if err != nil || dnsRecord.Payload.Type != r.recordType { + if err != nil { resp.Diagnostics.AddError( "Error reading Netlify DNS record", fmt.Sprintf( @@ -224,17 +232,19 @@ func (r *dnsRecordResource) Read(ctx context.Context, req resource.ReadRequest, ) return } + recordType := dnsRecord.Payload.Type + state.Type = types.StringValue(recordType) state.Hostname = types.StringValue(dnsRecord.Payload.Hostname) state.Value = types.StringValue(dnsRecord.Payload.Value) state.TTL = types.Int64Value(dnsRecord.Payload.TTL) - if r.recordType == "CAA" { + if recordType == "CAA" { state.Flag = types.Int64Value(dnsRecord.Payload.Flag) state.Tag = types.StringValue(dnsRecord.Payload.Tag) } else { state.Flag = types.Int64Null() state.Tag = types.StringNull() } - if r.recordType == "MX" { + if recordType == "MX" { state.Priority = types.Int64Value(dnsRecord.Payload.Priority) } else { state.Priority = types.Int64Null() diff --git a/internal/provider/netlify_validators/required_if_equals.go b/internal/provider/netlify_validators/required_if_equals.go new file mode 100644 index 0000000..17280b1 --- /dev/null +++ b/internal/provider/netlify_validators/required_if_equals.go @@ -0,0 +1,105 @@ +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.String = RequiredIfEqualsValidator{} +) + +type RequiredIfEqualsValidator struct { + PredicateValue string + PathExpressions path.Expressions +} + +type RequiredIfEqualsValidatorRequest struct { + Config tfsdk.Config + ConfigValue attr.Value + Path path.Path + PathExpression path.Expression +} + +type RequiredIfEqualsValidatorResponse struct { + Diagnostics diag.Diagnostics +} + +func (av RequiredIfEqualsValidator) Description(ctx context.Context) string { + return av.MarkdownDescription(ctx) +} + +func (av RequiredIfEqualsValidator) MarkdownDescription(_ context.Context) string { + return fmt.Sprintf("Ensure that if an attribute is set to %q, also these are set: %q", av.PredicateValue, av.PathExpressions) +} + +func (av RequiredIfEqualsValidator) Validate(ctx context.Context, req RequiredIfEqualsValidatorRequest, res *RequiredIfEqualsValidatorResponse) { + if req.ConfigValue.IsNull() || !req.ConfigValue.Equal(types.StringValue(av.PredicateValue)) { + return + } + + expressions := req.PathExpression.MergeExpressions(av.PathExpressions...) + + for _, expression := range expressions { + matchedPaths, diags := req.Config.PathMatches(ctx, expression) + + res.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + 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 + } + + if mpVal.IsNull() { + res.Diagnostics.Append(validatordiag.InvalidAttributeCombinationDiagnostic( + req.Path, + fmt.Sprintf("Attribute %q must be specified when %q is set to %q", req.Path, mp, av.PredicateValue), + )) + } + } + } +} + +func (av RequiredIfEqualsValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + validateReq := RequiredIfEqualsValidatorRequest{ + Config: req.Config, + ConfigValue: req.ConfigValue, + Path: req.Path, + PathExpression: req.PathExpression, + } + validateResp := &RequiredIfEqualsValidatorResponse{} + + av.Validate(ctx, validateReq, validateResp) + + resp.Diagnostics.Append(validateResp.Diagnostics...) +} + +func RequiredIfEquals(predicateValue string, pathExpressions ...path.Expression) validator.String { + return RequiredIfEqualsValidator{ + PredicateValue: predicateValue, + PathExpressions: path.Expressions(pathExpressions), + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 9f891e0..08375fd 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -156,15 +156,7 @@ func (p *NetlifyProvider) Configure(ctx context.Context, req provider.ConfigureR func (p *NetlifyProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ - NewDnsRecordResource("A"), - NewDnsRecordResource("AAAA"), - NewDnsRecordResource("ALIAS"), - NewDnsRecordResource("CAA"), - NewDnsRecordResource("CNAME"), - NewDnsRecordResource("MX"), - NewDnsRecordResource("NS"), - NewDnsRecordResource("SPF"), - NewDnsRecordResource("TXT"), + NewDnsRecordResource, NewDnsZoneResource, NewEnvironmentVariableResource, }