From ca752ff7e8c89ed81b3e3f43a061872df1b1e76b Mon Sep 17 00:00:00 2001 From: Gustavo Michels Date: Wed, 5 Apr 2023 14:37:51 -0400 Subject: [PATCH] feat: add management of DNS server configuration (#29) --- adguard/dns_config_data_source.go | 215 +++++++ adguard/dns_config_data_source_test.go | 43 ++ adguard/dns_config_resource.go | 524 ++++++++++++++++++ adguard/dns_config_resource_test.go | 89 +++ adguard/provider.go | 2 + adguard/validators.go | 61 ++ docs/data-sources/dns_config.md | 46 ++ docs/resources/dns_config.md | 65 +++ .../adguard_dns_config/data-source.tf | 3 + examples/main.tf | 77 +++ .../resources/adguard_dns_config/import.sh | 3 + .../resources/adguard_dns_config/resource.tf | 11 + go.mod | 6 +- go.sum | 11 +- 14 files changed, 1148 insertions(+), 8 deletions(-) create mode 100644 adguard/dns_config_data_source.go create mode 100644 adguard/dns_config_data_source_test.go create mode 100644 adguard/dns_config_resource.go create mode 100644 adguard/dns_config_resource_test.go create mode 100644 adguard/validators.go create mode 100644 docs/data-sources/dns_config.md create mode 100644 docs/resources/dns_config.md create mode 100644 examples/data-sources/adguard_dns_config/data-source.tf create mode 100644 examples/main.tf create mode 100644 examples/resources/adguard_dns_config/import.sh create mode 100644 examples/resources/adguard_dns_config/resource.tf diff --git a/adguard/dns_config_data_source.go b/adguard/dns_config_data_source.go new file mode 100644 index 0000000..b4804b9 --- /dev/null +++ b/adguard/dns_config_data_source.go @@ -0,0 +1,215 @@ +package adguard + +import ( + "context" + + "github.com/gmichels/adguard-client-go" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// ensure the implementation satisfies the expected interfaces +var ( + _ datasource.DataSource = &dnsConfigDataSource{} + _ datasource.DataSourceWithConfigure = &dnsConfigDataSource{} +) + +// dnsConfigDataSource is the data source implementation +type dnsConfigDataSource struct { + adg *adguard.ADG +} + +// dnsConfigDataModel maps DNS Config schema data +type dnsConfigDataModel struct { + ID types.String `tfsdk:"id"` + BootstrapDns types.List `tfsdk:"bootstrap_dns"` + UpstreamDns types.List `tfsdk:"upstream_dns"` + RateLimit types.Int64 `tfsdk:"rate_limit"` + BlockingMode types.String `tfsdk:"blocking_mode"` + BlockingIpv4 types.String `tfsdk:"blocking_ipv4"` + BlockingIpv6 types.String `tfsdk:"blocking_ipv6"` + EDnsCsEnabled types.Bool `tfsdk:"edns_cs_enabled"` + DisableIpv6 types.Bool `tfsdk:"disable_ipv6"` + DnsSecEnabled types.Bool `tfsdk:"dnssec_enabled"` + CacheSize types.Int64 `tfsdk:"cache_size"` + CacheTtlMin types.Int64 `tfsdk:"cache_ttl_min"` + CacheTtlMax types.Int64 `tfsdk:"cache_ttl_max"` + CacheOptimistic types.Bool `tfsdk:"cache_optimistic"` + UpstreamMode types.String `tfsdk:"upstream_mode"` + UsePrivatePtrResolvers types.Bool `tfsdk:"use_private_ptr_resolvers"` + ResolveClients types.Bool `tfsdk:"resolve_clients"` + LocalPtrUpstreams types.List `tfsdk:"local_ptr_upstreams"` + DefaultLocalPtrUpstreams types.List `tfsdk:"default_local_ptr_upstreams"` +} + +// NewDnsConfigDataSource is a helper function to simplify the provider implementation +func NewDnsConfigDataSource() datasource.DataSource { + return &dnsConfigDataSource{} +} + +// Metadata returns the data source type name +func (d *dnsConfigDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_dns_config" +} + +// Schema defines the schema for the data source +func (d *dnsConfigDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Identifier attribute", + Computed: true, + }, + "bootstrap_dns": schema.ListAttribute{ + Description: "Booststrap DNS servers", + ElementType: types.StringType, + Computed: true, + }, + "upstream_dns": schema.ListAttribute{ + Description: "Upstream DNS servers", + ElementType: types.StringType, + Computed: true, + }, + "rate_limit": schema.Int64Attribute{ + Description: "The number of requests per second allowed per client", + Computed: true, + }, + "blocking_mode": schema.StringAttribute{ + Description: "DNS response sent when request is blocked", + Computed: true, + }, + "blocking_ipv4": schema.StringAttribute{ + Description: "When `blocking_mode` is set to `custom_ip`, the IPv4 address to be returned for a blocked A request", + Computed: true, + }, + "blocking_ipv6": schema.StringAttribute{ + Description: "When `blocking_mode` is set to `custom_ip`, the IPv6 address to be returned for a blocked A request", + Computed: true, + }, + "edns_cs_enabled": schema.BoolAttribute{ + Description: "Whether EDNS Client Subnet (ECS) is enabled", + Computed: true, + }, + "disable_ipv6": schema.BoolAttribute{ + Description: "Whether dropping of all IPv6 DNS queries is enabled", + Computed: true, + }, + "dnssec_enabled": schema.BoolAttribute{ + Description: "Whether outgoing DNSSEC is enabled", + Computed: true, + }, + "cache_size": schema.Int64Attribute{ + Description: "DNS cache size (in bytes)", + Computed: true, + }, + "cache_ttl_min": schema.Int64Attribute{ + Description: "Overridden minimum TTL received from upstream DNS servers", + Computed: true, + }, + "cache_ttl_max": schema.Int64Attribute{ + Description: "Overridden maximum TTL received from upstream DNS servers", + Computed: true, + }, + "cache_optimistic": schema.BoolAttribute{ + Description: "Whether optimistic DNS caching is enabled", + Computed: true, + }, + "upstream_mode": schema.StringAttribute{ + Description: "Upstream DNS resolvers usage strategy", + Computed: true, + }, + "use_private_ptr_resolvers": schema.BoolAttribute{ + Description: "Whether to use private reverse DNS resolvers", + Computed: true, + }, + "resolve_clients": schema.BoolAttribute{ + Description: "Whether reverse DNS resolution of clients' IP addresses is enabled", + Computed: true, + }, + "local_ptr_upstreams": schema.ListAttribute{ + Description: "List of private reverse DNS servers", + ElementType: types.StringType, + Computed: true, + }, + "default_local_ptr_upstreams": schema.ListAttribute{ + Description: "List of discovered private reverse DNS servers", + ElementType: types.StringType, + Computed: true, + }, + }, + } +} + +// Read refreshes the Terraform state with the latest data +func (d *dnsConfigDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + // read Terraform configuration data into the model + var state dnsConfigDataModel + diags := req.Config.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + + // retrieve dnsConfig info + dnsConfig, err := d.adg.GetDnsInfo() + if err != nil { + resp.Diagnostics.AddError( + "Unable to Read AdGuard Home DNS Config", + err.Error(), + ) + return + } + + // map response body to model + state.BootstrapDns, diags = types.ListValueFrom(ctx, types.StringType, dnsConfig.BootstrapDns) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + state.UpstreamDns, diags = types.ListValueFrom(ctx, types.StringType, dnsConfig.UpstreamDns) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + state.RateLimit = types.Int64Value(int64(dnsConfig.RateLimit)) + state.BlockingMode = types.StringValue(dnsConfig.BlockingMode) + state.BlockingIpv4 = types.StringValue(dnsConfig.BlockingIpv4) + state.BlockingIpv6 = types.StringValue(dnsConfig.BlockingIpv6) + state.EDnsCsEnabled = types.BoolValue(dnsConfig.EDnsCsEnabled) + state.DisableIpv6 = types.BoolValue(dnsConfig.DisableIpv6) + state.DnsSecEnabled = types.BoolValue(dnsConfig.DnsSecEnabled) + state.CacheSize = types.Int64Value(int64(dnsConfig.CacheSize)) + state.CacheTtlMin = types.Int64Value(int64(dnsConfig.CacheTtlMin)) + state.CacheTtlMax = types.Int64Value(int64(dnsConfig.CacheTtlMax)) + state.CacheOptimistic = types.BoolValue(dnsConfig.CacheOptimistic) + state.UpstreamMode = types.StringValue(dnsConfig.UpstreamMode) + state.UsePrivatePtrResolvers = types.BoolValue(dnsConfig.UsePrivatePtrResolvers) + state.ResolveClients = types.BoolValue(dnsConfig.ResolveClients) + state.LocalPtrUpstreams, diags = types.ListValueFrom(ctx, types.StringType, dnsConfig.LocalPtrUpstreams) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + state.DefaultLocalPtrUpstreams, diags = types.ListValueFrom(ctx, types.StringType, dnsConfig.DefaultLocalPtrUpstreams) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // set ID placeholder for testing + state.ID = types.StringValue("placeholder") + + // set state + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Configure adds the provider configured dnsConfig to the data source +func (d *dnsConfigDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + d.adg = req.ProviderData.(*adguard.ADG) +} diff --git a/adguard/dns_config_data_source_test.go b/adguard/dns_config_data_source_test.go new file mode 100644 index 0000000..2d2e6a1 --- /dev/null +++ b/adguard/dns_config_data_source_test.go @@ -0,0 +1,43 @@ +package adguard + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDnsConfigDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Read testing + { + Config: providerConfig + `data "adguard_dns_config" "test" { }`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "bootstrap_dns.#", "4"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "bootstrap_dns.0", "9.9.9.10"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "upstream_dns.#", "1"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "upstream_dns.0", "https://dns10.quad9.net/dns-query"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "rate_limit", "20"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "blocking_mode", "default"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "blocking_ipv4", ""), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "blocking_ipv6", ""), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "edns_cs_enabled", "false"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "disable_ipv6", "false"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "dnssec_enabled", "false"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "cache_size", "4194304"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "cache_ttl_min", "0"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "cache_ttl_max", "0"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "cache_optimistic", "false"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "upstream_mode", ""), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "use_private_ptr_resolvers", "true"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "resolve_clients", "true"), + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "local_ptr_upstreams.#", "0"), + + // Verify placeholder id attribute + resource.TestCheckResourceAttr("data.adguard_dns_config.test", "id", "placeholder"), + ), + }, + }, + }) +} diff --git a/adguard/dns_config_resource.go b/adguard/dns_config_resource.go new file mode 100644 index 0000000..bd85efa --- /dev/null +++ b/adguard/dns_config_resource.go @@ -0,0 +1,524 @@ +package adguard + +import ( + "context" + "regexp" + "time" + + "github.com/gmichels/adguard-client-go" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "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/int64default" + "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" +) + +// ensure the implementation satisfies the expected interfaces +var ( + _ resource.Resource = &dnsConfigResource{} + _ resource.ResourceWithConfigure = &dnsConfigResource{} + _ resource.ResourceWithImportState = &dnsConfigResource{} +) + +// dnsConfigResource is the resource implementation +type dnsConfigResource struct { + adg *adguard.ADG +} + +// dnsConfigResourceModel maps DNS Config schema data +type dnsConfigResourceModel struct { + ID types.String `tfsdk:"id"` + LastUpdated types.String `tfsdk:"last_updated"` + BootstrapDns types.List `tfsdk:"bootstrap_dns"` + UpstreamDns types.List `tfsdk:"upstream_dns"` + RateLimit types.Int64 `tfsdk:"rate_limit"` + BlockingMode types.String `tfsdk:"blocking_mode"` + BlockingIpv4 types.String `tfsdk:"blocking_ipv4"` + BlockingIpv6 types.String `tfsdk:"blocking_ipv6"` + EDnsCsEnabled types.Bool `tfsdk:"edns_cs_enabled"` + DisableIpv6 types.Bool `tfsdk:"disable_ipv6"` + DnsSecEnabled types.Bool `tfsdk:"dnssec_enabled"` + CacheSize types.Int64 `tfsdk:"cache_size"` + CacheTtlMin types.Int64 `tfsdk:"cache_ttl_min"` + CacheTtlMax types.Int64 `tfsdk:"cache_ttl_max"` + CacheOptimistic types.Bool `tfsdk:"cache_optimistic"` + UpstreamMode types.String `tfsdk:"upstream_mode"` + UsePrivatePtrResolvers types.Bool `tfsdk:"use_private_ptr_resolvers"` + ResolveClients types.Bool `tfsdk:"resolve_clients"` + LocalPtrUpstreams types.List `tfsdk:"local_ptr_upstreams"` +} + +// NewDnsConfigResource is a helper function to simplify the provider implementation +func NewDnsConfigResource() resource.Resource { + return &dnsConfigResource{} +} + +// Metadata returns the resource type name +func (r *dnsConfigResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_dns_config" +} + +// Schema defines the schema for the resource +func (r *dnsConfigResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Internal identifier for this dnsConfig", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "last_updated": schema.StringAttribute{ + Description: "Timestamp of the last Terraform update of the dnsConfig", + Computed: true, + }, + "bootstrap_dns": schema.ListAttribute{ + Description: "Booststrap DNS servers", + ElementType: types.StringType, + Optional: true, + Computed: true, + Validators: []validator.List{listvalidator.SizeAtLeast(1)}, + Default: listdefault.StaticValue( + types.ListValueMust( + types.StringType, + []attr.Value{ + types.StringValue("9.9.9.10"), + types.StringValue("149.112.112.10"), + types.StringValue("2620:fe::10"), + types.StringValue("2620:fe::fe:10"), + }, + ), + ), + }, + "upstream_dns": schema.ListAttribute{ + Description: "Upstream DNS servers", + ElementType: types.StringType, + Optional: true, + Computed: true, + Validators: []validator.List{listvalidator.SizeAtLeast(1)}, + Default: listdefault.StaticValue( + types.ListValueMust( + types.StringType, + []attr.Value{ + types.StringValue("https://dns10.quad9.net/dns-query"), + }, + ), + ), + }, + "rate_limit": schema.Int64Attribute{ + Description: "The number of requests per second allowed per client", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(20), + }, + "blocking_mode": schema.StringAttribute{ + Description: "DNS response sent when request is blocked", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("default"), + Validators: []validator.String{ + stringvalidator.OneOf("default", "refused", "nxdomain", "null_ip", "custom_ip"), + }, + }, + "blocking_ipv4": schema.StringAttribute{ + Description: "When `blocking_mode` is set to `custom_ip`, the IPv4 address to be returned for a blocked A request", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + Validators: []validator.String{ + stringvalidator.All( + stringvalidator.RegexMatches( + regexp.MustCompile(`\b((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.|$)){4}\b`), + "must be a valid IPv4 address", + ), + checkBlockingMode("custom_ip"), + stringvalidator.AlsoRequires(path.Expressions{ + path.MatchRoot("blocking_mode"), + path.MatchRoot("blocking_ipv6"), + }...), + ), + }, + }, + "blocking_ipv6": schema.StringAttribute{ + Description: "When `blocking_mode` is set to `custom_ip`, the IPv6 address to be returned for a blocked A request", + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + Validators: []validator.String{ + stringvalidator.All( + stringvalidator.RegexMatches( + regexp.MustCompile(`(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))`), + "must be a valid IPv6 address", + ), + checkBlockingMode("custom_ip"), + stringvalidator.AlsoRequires(path.Expressions{ + path.MatchRoot("blocking_mode"), + path.MatchRoot("blocking_ipv4"), + }...), + ), + }, + }, + "edns_cs_enabled": schema.BoolAttribute{ + Description: "Whether EDNS Client Subnet (ECS) is enabled", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "disable_ipv6": schema.BoolAttribute{ + Description: "Whether dropping of all IPv6 DNS queries is enabled", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "dnssec_enabled": schema.BoolAttribute{ + Description: "Whether outgoing DNSSEC is enabled", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "cache_size": schema.Int64Attribute{ + Description: "DNS cache size (in bytes)", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(4194304), + }, + "cache_ttl_min": schema.Int64Attribute{ + Description: "Overridden minimum TTL received from upstream DNS servers", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "cache_ttl_max": schema.Int64Attribute{ + Description: "Overridden maximum TTL received from upstream DNS servers", + Optional: true, + Computed: true, + Default: int64default.StaticInt64(0), + }, + "cache_optimistic": schema.BoolAttribute{ + Description: "Whether optimistic DNS caching is enabled", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + }, + "upstream_mode": schema.StringAttribute{ + Description: "Upstream DNS resolvers usage strategy", + Optional: true, + Computed: true, + Default: stringdefault.StaticString("load_balance"), + Validators: []validator.String{ + stringvalidator.OneOf("load_balance", "parallel", "fastest_addr"), + }, + }, + "use_private_ptr_resolvers": schema.BoolAttribute{ + Description: "Whether to use private reverse DNS resolvers", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "resolve_clients": schema.BoolAttribute{ + Description: "Whether reverse DNS resolution of clients' IP addresses is enabled", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + }, + "local_ptr_upstreams": schema.ListAttribute{ + Description: "List of private reverse DNS servers", + ElementType: types.StringType, + Optional: true, + Validators: []validator.List{listvalidator.SizeAtLeast(1)}, + }, + }, + } +} + +// Configure adds the provider configured DNS Config to the resource +func (r *dnsConfigResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + r.adg = req.ProviderData.(*adguard.ADG) +} + +// Create creates the resource and sets the initial Terraform state +func (r *dnsConfigResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // retrieve values from plan + var plan dnsConfigResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // instantiate empty DNS Config for storing plan data + var dnsConfig adguard.DNSConfig + + // populate DNS Config from plan + if len(plan.BootstrapDns.Elements()) > 0 { + diags = plan.BootstrapDns.ElementsAs(ctx, &dnsConfig.BootstrapDns, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + if len(plan.UpstreamDns.Elements()) > 0 { + diags = plan.UpstreamDns.ElementsAs(ctx, &dnsConfig.UpstreamDns, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + dnsConfig.RateLimit = uint(plan.RateLimit.ValueInt64()) + dnsConfig.BlockingMode = plan.BlockingMode.ValueString() + dnsConfig.BlockingIpv4 = plan.BlockingIpv4.ValueString() + dnsConfig.BlockingIpv6 = plan.BlockingIpv6.ValueString() + dnsConfig.EDnsCsEnabled = plan.EDnsCsEnabled.ValueBool() + dnsConfig.DisableIpv6 = plan.DisableIpv6.ValueBool() + dnsConfig.DnsSecEnabled = plan.DnsSecEnabled.ValueBool() + dnsConfig.CacheSize = uint(plan.CacheSize.ValueInt64()) + dnsConfig.CacheTtlMin = uint(plan.CacheTtlMin.ValueInt64()) + dnsConfig.CacheTtlMax = uint(plan.CacheTtlMax.ValueInt64()) + dnsConfig.CacheOptimistic = plan.CacheOptimistic.ValueBool() + if plan.UpstreamMode.ValueString() == "load_balance" { + dnsConfig.UpstreamMode = "" + } else { + dnsConfig.UpstreamMode = plan.UpstreamMode.ValueString() + } + dnsConfig.UsePrivatePtrResolvers = plan.UsePrivatePtrResolvers.ValueBool() + dnsConfig.ResolveClients = plan.ResolveClients.ValueBool() + if len(plan.LocalPtrUpstreams.Elements()) > 0 { + diags = plan.LocalPtrUpstreams.ElementsAs(ctx, &dnsConfig.LocalPtrUpstreams, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + + // set DNS Config using plan + _, err := r.adg.SetDnsConfig(dnsConfig) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating DNS Config", + "Could not create DNS Config, unexpected error: "+err.Error(), + ) + return + } + + // response sent by AdGuard Home is the same as the sent payload, + // just add missing attributes for state + // there can be only one entry DNS Config, so hardcode the ID as 1 + plan.ID = types.StringValue("1") + // add the last updated attribute + plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) + + // set state to fully populated data + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read refreshes the Terraform state with the latest data +func (r *dnsConfigResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // get current state + var state dnsConfigResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // get refreshed DNS dnsConfig rule value from AdGuard Home + dnsConfig, err := r.adg.GetDnsInfo() + if err != nil { + resp.Diagnostics.AddError( + "Error Reading AdGuard Home DNS Config", + "Could not read AdGuard Home DNS Config with ID "+state.ID.ValueString()+": "+err.Error(), + ) + return + } + + // overwrite DNS dnsConfig rule with refreshed state + state.BootstrapDns, diags = types.ListValueFrom(ctx, types.StringType, dnsConfig.BootstrapDns) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + state.UpstreamDns, diags = types.ListValueFrom(ctx, types.StringType, dnsConfig.UpstreamDns) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + state.RateLimit = types.Int64Value(int64(dnsConfig.RateLimit)) + state.BlockingMode = types.StringValue(dnsConfig.BlockingMode) + // upstream API does not unset blocking_ipv4 and blocking_ipv6 when previously set + // and blocking mode changes, so force state to empty values here + if dnsConfig.BlockingMode != "custom_ip" { + state.BlockingIpv4 = types.StringValue("") + state.BlockingIpv6 = types.StringValue("") + } else { + state.BlockingIpv4 = types.StringValue(dnsConfig.BlockingIpv4) + state.BlockingIpv6 = types.StringValue(dnsConfig.BlockingIpv6) + } + state.EDnsCsEnabled = types.BoolValue(dnsConfig.EDnsCsEnabled) + state.DisableIpv6 = types.BoolValue(dnsConfig.DisableIpv6) + state.DnsSecEnabled = types.BoolValue(dnsConfig.DnsSecEnabled) + state.CacheSize = types.Int64Value(int64(dnsConfig.CacheSize)) + state.CacheTtlMin = types.Int64Value(int64(dnsConfig.CacheTtlMin)) + state.CacheTtlMax = types.Int64Value(int64(dnsConfig.CacheTtlMax)) + state.CacheOptimistic = types.BoolValue(dnsConfig.CacheOptimistic) + if dnsConfig.UpstreamMode != "" { + state.UpstreamMode = types.StringValue(dnsConfig.UpstreamMode) + } else { + state.UpstreamMode = types.StringValue("load_balance") + } + state.UsePrivatePtrResolvers = types.BoolValue(dnsConfig.UsePrivatePtrResolvers) + state.ResolveClients = types.BoolValue(dnsConfig.ResolveClients) + if len(dnsConfig.LocalPtrUpstreams) > 0 { + state.LocalPtrUpstreams, diags = types.ListValueFrom(ctx, types.StringType, dnsConfig.LocalPtrUpstreams) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + // set refreshed state + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Update updates the resource and sets the updated Terraform state on success +func (r *dnsConfigResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // updating is exactly the same as creating and unfortunately I don't know + // of a way to reuse the code due to the different argument types, hence the duplication + + // retrieve values from plan + var plan dnsConfigResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // instantiate empty DNS Config for storing plan data + var dnsConfig adguard.DNSConfig + + // populate DNS Config from plan + if len(plan.BootstrapDns.Elements()) > 0 { + diags = plan.BootstrapDns.ElementsAs(ctx, &dnsConfig.BootstrapDns, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + if len(plan.UpstreamDns.Elements()) > 0 { + diags = plan.UpstreamDns.ElementsAs(ctx, &dnsConfig.UpstreamDns, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } + dnsConfig.RateLimit = uint(plan.RateLimit.ValueInt64()) + dnsConfig.BlockingMode = plan.BlockingMode.ValueString() + dnsConfig.BlockingIpv4 = plan.BlockingIpv4.ValueString() + dnsConfig.BlockingIpv6 = plan.BlockingIpv6.ValueString() + dnsConfig.EDnsCsEnabled = plan.EDnsCsEnabled.ValueBool() + dnsConfig.DisableIpv6 = plan.DisableIpv6.ValueBool() + dnsConfig.DnsSecEnabled = plan.DnsSecEnabled.ValueBool() + dnsConfig.CacheSize = uint(plan.CacheSize.ValueInt64()) + dnsConfig.CacheTtlMin = uint(plan.CacheTtlMin.ValueInt64()) + dnsConfig.CacheTtlMax = uint(plan.CacheTtlMax.ValueInt64()) + dnsConfig.CacheOptimistic = plan.CacheOptimistic.ValueBool() + if plan.UpstreamMode.ValueString() == "load_balance" { + dnsConfig.UpstreamMode = "" + } else { + dnsConfig.UpstreamMode = plan.UpstreamMode.ValueString() + } + dnsConfig.UsePrivatePtrResolvers = plan.UsePrivatePtrResolvers.ValueBool() + dnsConfig.ResolveClients = plan.ResolveClients.ValueBool() + if len(plan.LocalPtrUpstreams.Elements()) > 0 { + diags = plan.LocalPtrUpstreams.ElementsAs(ctx, &dnsConfig.LocalPtrUpstreams, false) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } else { + dnsConfig.LocalPtrUpstreams = []string{} + } + + // set DNS Config using plan + _, err := r.adg.SetDnsConfig(dnsConfig) + if err != nil { + resp.Diagnostics.AddError( + "Error Creating DNS Config", + "Could not create DNS Config, unexpected error: "+err.Error(), + ) + return + } + + // update resource state with updated items and timestamp + plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) + + // update state + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Delete deletes the resource and removes the Terraform state on success +func (r *dnsConfigResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // there is no "real" delete for DNS Configs, so this means "restore defaults" + + // instantiate empty DNS Config for storing default values + var dnsConfig adguard.DNSConfig + + // populate DNS Config with default values + dnsConfig.BootstrapDns = []string{"9.9.9.10", "149.112.112.10", "2620:fe::10", "2620:fe::fe:10"} + dnsConfig.UpstreamDns = []string{"https://dns10.quad9.net/dns-query"} + dnsConfig.UpstreamDnsFile = "" + dnsConfig.RateLimit = 20 + dnsConfig.BlockingMode = "default" + dnsConfig.BlockingIpv4 = "" + dnsConfig.BlockingIpv6 = "" + dnsConfig.EDnsCsEnabled = false + dnsConfig.DisableIpv6 = false + dnsConfig.DnsSecEnabled = false + dnsConfig.CacheSize = 4194304 + dnsConfig.CacheTtlMin = 0 + dnsConfig.CacheTtlMax = 0 + dnsConfig.CacheOptimistic = false + dnsConfig.UpstreamMode = "" + dnsConfig.UsePrivatePtrResolvers = true + dnsConfig.ResolveClients = true + dnsConfig.LocalPtrUpstreams = []string{} + + // set default values in DNS Config + _, err := r.adg.SetDnsConfig(dnsConfig) + if err != nil { + resp.Diagnostics.AddError( + "Error Deleting DNS Config", + "Could not delete DNS Config, unexpected error: "+err.Error(), + ) + return + } +} + +func (r *dnsConfigResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // retrieve import ID and save to id attribute + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/adguard/dns_config_resource_test.go b/adguard/dns_config_resource_test.go new file mode 100644 index 0000000..084928a --- /dev/null +++ b/adguard/dns_config_resource_test.go @@ -0,0 +1,89 @@ +package adguard + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccDnsConfigResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read testing + { + Config: providerConfig + ` +resource "adguard_dns_config" "test" { + upstream_dns = ["https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"] + rate_limit = 30 + cache_ttl_min = 600 + cache_ttl_max = 86400 + cache_optimistic = true + blocking_mode = "custom_ip" + blocking_ipv4 = "1.2.3.4" + blocking_ipv6 = "fe80::" + local_ptr_upstreams = ["192.168.0.1", "192.168.0.2"] +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("adguard_dns_config.test", "upstream_dns.#", "2"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "upstream_dns.1", "https://1.0.0.1/dns-query"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "rate_limit", "30"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "blocking_mode", "custom_ip"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "blocking_ipv4", "1.2.3.4"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "blocking_ipv6", "fe80::"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "cache_ttl_min", "600"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "cache_ttl_max", "86400"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "cache_optimistic", "true"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "local_ptr_upstreams.#", "2"), + // Verify dynamic values have any value set in the state. + resource.TestCheckResourceAttrSet("adguard_dns_config.test", "id"), + resource.TestCheckResourceAttrSet("adguard_dns_config.test", "last_updated"), + ), + }, + // ImportState testing + { + ResourceName: "adguard_dns_config.test", + ImportState: true, + ImportStateVerify: true, + // The last_updated attribute does not exist in AdGuard Home, + // therefore there is no value for it during import + ImportStateVerifyIgnore: []string{"last_updated"}, + }, + // Update and Read testing + { + Config: providerConfig + ` +resource "adguard_dns_config" "test" { + upstream_dns = ["https://1.1.1.1/dns-query"] + blocking_mode = "nxdomain" + rate_limit = 25 + edns_cs_enabled = true + disable_ipv6 = true + dnssec_enabled = true + cache_size = 8000000 + upstream_mode = "load_balance" + use_private_ptr_resolvers = false + resolve_clients = false +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("adguard_dns_config.test", "upstream_dns.#", "1"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "upstream_dns.0", "https://1.1.1.1/dns-query"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "rate_limit", "25"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "blocking_mode", "nxdomain"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "blocking_ipv4", ""), + resource.TestCheckResourceAttr("adguard_dns_config.test", "blocking_ipv6", ""), + resource.TestCheckResourceAttr("adguard_dns_config.test", "edns_cs_enabled", "true"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "disable_ipv6", "true"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "dnssec_enabled", "true"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "cache_size", "8000000"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "upstream_mode", "load_balance"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "use_private_ptr_resolvers", "false"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "resolve_clients", "false"), + resource.TestCheckResourceAttr("adguard_dns_config.test", "local_ptr_upstreams.#", "0"), + ), + }, + // Delete testing automatically occurs in TestCase + }, + }) +} diff --git a/adguard/provider.go b/adguard/provider.go index 2a822a6..0fcdd76 100644 --- a/adguard/provider.go +++ b/adguard/provider.go @@ -286,6 +286,7 @@ func (p *adguardProvider) DataSources(_ context.Context) []func() datasource.Dat NewListFilterDataSource, NewUserRulesDataSource, NewRewriteDataSource, + NewDnsConfigDataSource, } } @@ -296,5 +297,6 @@ func (p *adguardProvider) Resources(_ context.Context) []func() resource.Resourc NewListFilterResource, NewUserRulesResource, NewRewriteResource, + NewDnsConfigResource, } } diff --git a/adguard/validators.go b/adguard/validators.go new file mode 100644 index 0000000..76de114 --- /dev/null +++ b/adguard/validators.go @@ -0,0 +1,61 @@ +package adguard + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// Blocking Mode validator confirms the DNS Config blocking mode is correctly configured +var _ validator.String = checkBlockingModeValidator{} + +type checkBlockingModeValidator struct { + mode string +} + +func (v checkBlockingModeValidator) Description(_ context.Context) string { + return fmt.Sprintf("blocking_mode must be set to %s when specifying blocking_ipv4 and blocking_ipv6", v.mode) +} + +func (v checkBlockingModeValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +func (v checkBlockingModeValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + // If code block does not exist, config is valid. + return + } + + modePath := req.Path.ParentPath().AtName("blocking_mode") + + var m types.String + + diags := req.Config.GetAttribute(ctx, modePath, &m) + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + if m.IsNull() || m.IsUnknown() { + // Only validate if mode value is known. + return + } + + if m.ValueString() != v.mode { + resp.Diagnostics.AddAttributeError( + modePath, + "DNS Config Blocking Mode Value Invalid", + v.Description(ctx), + ) + } +} + +func checkBlockingMode(m string) validator.String { + return checkBlockingModeValidator{ + mode: m, + } +} diff --git a/docs/data-sources/dns_config.md b/docs/data-sources/dns_config.md new file mode 100644 index 0000000..71cd035 --- /dev/null +++ b/docs/data-sources/dns_config.md @@ -0,0 +1,46 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "adguard_dns_config Data Source - adguard" +subcategory: "" +description: |- + +--- + +# adguard_dns_config (Data Source) + + + +## Example Usage + +```terraform +# get the current DNS config +data "adguard_dns_config" "test" { +} +``` + + +## Schema + +### Read-Only + +- `blocking_ipv4` (String) When `blocking_mode` is set to `custom_ip`, the IPv4 address to be returned for a blocked A request +- `blocking_ipv6` (String) When `blocking_mode` is set to `custom_ip`, the IPv6 address to be returned for a blocked A request +- `blocking_mode` (String) DNS response sent when request is blocked +- `bootstrap_dns` (List of String) Booststrap DNS servers +- `cache_optimistic` (Boolean) Whether optimistic DNS caching is enabled +- `cache_size` (Number) DNS cache size (in bytes) +- `cache_ttl_max` (Number) Overridden maximum TTL received from upstream DNS servers +- `cache_ttl_min` (Number) Overridden minimum TTL received from upstream DNS servers +- `default_local_ptr_upstreams` (List of String) List of discovered private reverse DNS servers +- `disable_ipv6` (Boolean) Whether dropping of all IPv6 DNS queries is enabled +- `dnssec_enabled` (Boolean) Whether outgoing DNSSEC is enabled +- `edns_cs_enabled` (Boolean) Whether EDNS Client Subnet (ECS) is enabled +- `id` (String) Identifier attribute +- `local_ptr_upstreams` (List of String) List of private reverse DNS servers +- `rate_limit` (Number) The number of requests per second allowed per client +- `resolve_clients` (Boolean) Whether reverse DNS resolution of clients' IP addresses is enabled +- `upstream_dns` (List of String) Upstream DNS servers +- `upstream_mode` (String) Upstream DNS resolvers usage strategy +- `use_private_ptr_resolvers` (Boolean) Whether to use private reverse DNS resolvers + + diff --git a/docs/resources/dns_config.md b/docs/resources/dns_config.md new file mode 100644 index 0000000..46d031b --- /dev/null +++ b/docs/resources/dns_config.md @@ -0,0 +1,65 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "adguard_dns_config Resource - adguard" +subcategory: "" +description: |- + +--- + +# adguard_dns_config (Resource) + + + +## Example Usage + +```terraform +# manage the DNS configuration +# NOTE: there can only be 1 (one) `adguard_dns_config` resource +# specifying multiple resources will result in errors +resource "adguard_dns_config" "test" { + upstream_dns = ["https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"] + rate_limit = 30 + cache_ttl_min = 600 + cache_ttl_max = 86400 + cache_optimistic = true + local_ptr_upstreams = ["192.168.0.1", "192.168.0.2"] +} +``` + + +## Schema + +### Optional + +- `blocking_ipv4` (String) When `blocking_mode` is set to `custom_ip`, the IPv4 address to be returned for a blocked A request +- `blocking_ipv6` (String) When `blocking_mode` is set to `custom_ip`, the IPv6 address to be returned for a blocked A request +- `blocking_mode` (String) DNS response sent when request is blocked +- `bootstrap_dns` (List of String) Booststrap DNS servers +- `cache_optimistic` (Boolean) Whether optimistic DNS caching is enabled +- `cache_size` (Number) DNS cache size (in bytes) +- `cache_ttl_max` (Number) Overridden maximum TTL received from upstream DNS servers +- `cache_ttl_min` (Number) Overridden minimum TTL received from upstream DNS servers +- `disable_ipv6` (Boolean) Whether dropping of all IPv6 DNS queries is enabled +- `dnssec_enabled` (Boolean) Whether outgoing DNSSEC is enabled +- `edns_cs_enabled` (Boolean) Whether EDNS Client Subnet (ECS) is enabled +- `local_ptr_upstreams` (List of String) List of private reverse DNS servers +- `rate_limit` (Number) The number of requests per second allowed per client +- `resolve_clients` (Boolean) Whether reverse DNS resolution of clients' IP addresses is enabled +- `upstream_dns` (List of String) Upstream DNS servers +- `upstream_mode` (String) Upstream DNS resolvers usage strategy +- `use_private_ptr_resolvers` (Boolean) Whether to use private reverse DNS resolvers + +### Read-Only + +- `id` (String) Internal identifier for this dnsConfig +- `last_updated` (String) Timestamp of the last Terraform update of the dnsConfig + +## Import + +Import is supported using the following syntax: + +```shell +# DNS config can be imported by specifying the ID as `1` +# NOTE: there can only be 1 (one) `adguard_dns_config` resource, hence the hardcoded ID +terraform import adguard_dns_config.test "1" +``` diff --git a/examples/data-sources/adguard_dns_config/data-source.tf b/examples/data-sources/adguard_dns_config/data-source.tf new file mode 100644 index 0000000..bdab4e2 --- /dev/null +++ b/examples/data-sources/adguard_dns_config/data-source.tf @@ -0,0 +1,3 @@ +# get the current DNS config +data "adguard_dns_config" "test" { +} diff --git a/examples/main.tf b/examples/main.tf new file mode 100644 index 0000000..b827c6e --- /dev/null +++ b/examples/main.tf @@ -0,0 +1,77 @@ +terraform { + required_providers { + adguard = { + source = "gmichels/adguard" + version = "0.2.0" + } + } +} + +# configuration for the provider +provider "adguard" { + host = "localhost:8080" + username = "admin" + password = "SecretP@ssw0rd" + scheme = "http" # defaults to https + timeout = 5 # in seconds, defaults to 10 +} + +# resource "adguard_list_filter" "test_blacklist" { +# name = "Test Blacklist Filtering" +# url = "https://adguardteam.github.io/HostlistsRegistry/assets/filter_4.txt" +# } + +# resource "adguard_client" "test" { +# name = "Test Client Updated" +# ids = ["192.168.100.1", "test-clienet"] +# # upstreams = ["1.2.3.4"] +# } + +# resource "adguard_user_rules" "testing" { +# rules = [ +# "! line 1 bang commen", +# "# line 2 respond with 127.0.0.1 for localhost.org (but not for its subdomains)", +# "127.0.0.1 localhost.org", +# "# line 4 unblock access to unblocked.org and all its subdomains", +# "@@||unblocked.org^", +# "# line 6 block access to blocked.org and all its subdomains", +# "||blocked.org^" +# ] +# } + +# resource "adguard_rewrite" "test" { +# domain = "example.com" +# answer = "4.3.2.4" +# } + +# resource "adguard_rewrite" "test_new" { +# domain = "example.org" +# answer = "example.com" +# } + + +resource "adguard_dns_config" "test" { + upstream_dns = ["https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"] + rate_limit = 30 + cache_ttl_min = 600 + cache_ttl_max = 86400 + cache_optimistic = true + # blocking_mode = "custom_ip" + # blocking_ipv4 = "1.2.3.4" + # blocking_ipv6 = "fe80::" + local_ptr_upstreams = ["192.168.0.1", "192.168.0.2"] +} + + +# resource "adguard_dns_config" "test" { +# upstream_dns = ["https://1.1.1.1/dns-query"] +# blocking_mode = "nxdomain" +# rate_limit = 25 +# edns_cs_enabled = true +# disable_ipv6 = true +# dnssec_enabled = true +# cache_size = 8000000 +# upstream_mode = "load_balance" +# use_private_ptr_resolvers = false +# resolve_clients = false +# } diff --git a/examples/resources/adguard_dns_config/import.sh b/examples/resources/adguard_dns_config/import.sh new file mode 100644 index 0000000..462921b --- /dev/null +++ b/examples/resources/adguard_dns_config/import.sh @@ -0,0 +1,3 @@ +# DNS config can be imported by specifying the ID as `1` +# NOTE: there can only be 1 (one) `adguard_dns_config` resource, hence the hardcoded ID +terraform import adguard_dns_config.test "1" diff --git a/examples/resources/adguard_dns_config/resource.tf b/examples/resources/adguard_dns_config/resource.tf new file mode 100644 index 0000000..2c0999e --- /dev/null +++ b/examples/resources/adguard_dns_config/resource.tf @@ -0,0 +1,11 @@ +# manage the DNS configuration +# NOTE: there can only be 1 (one) `adguard_dns_config` resource +# specifying multiple resources will result in errors +resource "adguard_dns_config" "test" { + upstream_dns = ["https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"] + rate_limit = 30 + cache_ttl_min = 600 + cache_ttl_max = 86400 + cache_optimistic = true + local_ptr_upstreams = ["192.168.0.1", "192.168.0.2"] +} diff --git a/go.mod b/go.mod index ddd6ace..e509265 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ go 1.20 // replace github.com/gmichels/adguard-client-go => /path/to/adguard-client-go require ( - github.com/gmichels/adguard-client-go v0.3.0 + github.com/gmichels/adguard-client-go v0.4.0 github.com/hashicorp/terraform-plugin-docs v0.14.1 github.com/hashicorp/terraform-plugin-framework v1.2.0 github.com/hashicorp/terraform-plugin-framework-validators v0.10.0 @@ -66,10 +66,10 @@ require ( golang.org/x/crypto v0.7.0 // indirect golang.org/x/mod v0.9.0 // indirect golang.org/x/net v0.8.0 // indirect - golang.org/x/sys v0.6.0 // indirect + golang.org/x/sys v0.7.0 // indirect golang.org/x/text v0.8.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230327152035-dc694ad2151e // indirect + google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect google.golang.org/grpc v1.54.0 // indirect google.golang.org/protobuf v1.30.0 // indirect ) diff --git a/go.sum b/go.sum index e2280a7..9cff1de 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBD github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= -github.com/gmichels/adguard-client-go v0.3.0 h1:u5JI4Zml7wxWXqbeWyPqwFgSNtOKMz7oT3Av2/u3GfI= -github.com/gmichels/adguard-client-go v0.3.0/go.mod h1:77E8dDI1mhRP5E7tNJWvtF2PLqZiqwcmMrgBUCCc4KY= +github.com/gmichels/adguard-client-go v0.4.0 h1:ECpjSuAI929uyDwKfrI4p95B1oqr3di65nOW3shaC/0= +github.com/gmichels/adguard-client-go v0.4.0/go.mod h1:77E8dDI1mhRP5E7tNJWvtF2PLqZiqwcmMrgBUCCc4KY= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= @@ -257,8 +257,9 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -279,8 +280,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20230327152035-dc694ad2151e h1:rRGPYd0STm9H4Ci+iGrSLG35mkAKY41/nzCcG7PQADw= -google.golang.org/genproto v0.0.0-20230327152035-dc694ad2151e/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU= +google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=