Skip to content

Commit 1eb0838

Browse files
feat(dns): implement observability extension
feat(dns): implement observability extension
1 parent d203bdc commit 1eb0838

7 files changed

Lines changed: 143 additions & 69 deletions

File tree

docs/data-sources/dns_zone.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ data "stackit_dns_zone" "example" {
4040
- `default_ttl` (Number) Default time to live.
4141
- `description` (String) Description of the zone.
4242
- `expire_time` (Number) Expire time.
43+
- `extensions` (Attributes) A single extensions block as defined below. (see [below for nested schema](#nestedatt--extensions))
4344
- `id` (String) Terraform's internal data source. ID. It is structured as "`project_id`,`zone_id`".
4445
- `is_reverse_zone` (Boolean) Specifies, if the zone is a reverse zone or not.
4546
- `name` (String) The user given name of the zone.
@@ -60,3 +61,19 @@ data "stackit_dns_zone" "example" {
6061
Optional:
6162

6263
- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
64+
65+
66+
<a id="nestedatt--extensions"></a>
67+
### Nested Schema for `extensions`
68+
69+
Read-Only:
70+
71+
- `observability` (Attributes) A single observability block as defined below. (see [below for nested schema](#nestedatt--extensions--observability))
72+
73+
<a id="nestedatt--extensions--observability"></a>
74+
### Nested Schema for `extensions.observability`
75+
76+
Read-Only:
77+
78+
- `observability_instance_id` (String) Observability instance ID to choose which Observability instance is used.
79+
- `state` (String) State of the observability extension.

docs/resources/dns_zone.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {
4848
- `default_ttl` (Number) Default time to live. E.g. 3600.
4949
- `description` (String) Description of the zone.
5050
- `expire_time` (Number) Expire time. E.g. 1209600.
51+
- `extensions` (Attributes) A single extensions block as defined below. (see [below for nested schema](#nestedatt--extensions))
5152
- `is_reverse_zone` (Boolean) Specifies, if the zone is a reverse zone or not. Defaults to `false`
5253
- `negative_cache` (Number) Negative caching. E.g. 60
5354
- `primaries` (List of String) Primary name server for secondary zone. E.g. ["1.2.3.4"]
@@ -66,6 +67,26 @@ import {
6667
- `visibility` (String) Visibility of the zone. E.g. `public`.
6768
- `zone_id` (String) The zone ID.
6869

70+
<a id="nestedatt--extensions"></a>
71+
### Nested Schema for `extensions`
72+
73+
Optional:
74+
75+
- `observability` (Attributes) A single observability block as defined below. (see [below for nested schema](#nestedatt--extensions--observability))
76+
77+
<a id="nestedatt--extensions--observability"></a>
78+
### Nested Schema for `extensions.observability`
79+
80+
Required:
81+
82+
- `observability_instance_id` (String) Observability instance ID to choose which Observability instance is used.
83+
84+
Read-Only:
85+
86+
- `state` (String) State of the extension.
87+
88+
89+
6990
<a id="nestedatt--timeouts"></a>
7091
### Nested Schema for `timeouts`
7192

stackit/internal/services/dns/dns_acc_test.go

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,28 +40,32 @@ var testConfigVarsMin = config.Variables{
4040
}
4141

4242
var testConfigVarsMax = config.Variables{
43-
"project_id": config.StringVariable(testutil.ProjectId),
44-
"name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
45-
"dns_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + ".example.home"),
46-
"acl": config.StringVariable("0.0.0.0/0"),
47-
"active": config.BoolVariable(true),
48-
"contact_email": config.StringVariable("contact@example.com"),
49-
"default_ttl": config.IntegerVariable(3600),
50-
"description": config.StringVariable("a test description"),
51-
"expire_time": config.IntegerVariable(1 * 24 * 60 * 60),
43+
"project_id": config.StringVariable(testutil.ProjectId),
44+
"name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
45+
"dns_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha) + ".example.home"),
46+
"acl": config.StringVariable("0.0.0.0/0"),
47+
"active": config.BoolVariable(true),
48+
"contact_email": config.StringVariable("contact@example.com"),
49+
"default_ttl": config.IntegerVariable(3600),
50+
"description": config.StringVariable("a test description"),
51+
"expire_time": config.IntegerVariable(1 * 24 * 60 * 60),
52+
"ext_observability_instance_id": config.StringVariable("a8f823ad-3abb-47fb-b56a-e3a7e97f65d5"),
53+
// "ext_observability_state": config.StringVariable("disabled"),
5254
"is_reverse_zone": config.BoolVariable(false),
5355
// "negative_cache": config.IntegerVariable(128),
5456
"primaries": config.ListVariable(config.StringVariable("1.1.1.1")),
5557
"refresh_time": config.IntegerVariable(3600),
5658
"retry_time": config.IntegerVariable(600),
5759
"type": config.StringVariable("primary"),
5860

59-
"record_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
60-
"record_record1": config.StringVariable("1.2.3.4"),
61-
"record_active": config.BoolVariable(true),
62-
"record_comment": config.StringVariable("a test comment"),
63-
"record_ttl": config.IntegerVariable(3600),
64-
"record_type": config.StringVariable("A"),
61+
"record_name": config.StringVariable("tf-acc-" + acctest.RandStringFromCharSet(8, acctest.CharSetAlpha)),
62+
"record_record1": config.StringVariable("1.2.3.4"),
63+
"record_active": config.BoolVariable(true),
64+
"record_comment": config.StringVariable("a test comment"),
65+
"record_ttl": config.IntegerVariable(3600),
66+
"record_type": config.StringVariable("A"),
67+
"observability_instance_name": config.StringVariable(fmt.Sprintf("tf-acc-i%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
68+
"observability_plan_name": config.StringVariable("Observability-Metrics-Endpoint-100k-EU01"),
6569
}
6670

6771
func configVarsInvalid(vars config.Variables) config.Variables {
@@ -313,6 +317,9 @@ func TestAccDnsMaxResource(t *testing.T) {
313317
resource.TestCheckResourceAttr("stackit_dns_record_set.record_set", "type", testutil.ConvertConfigVariable(testConfigVarsMax["record_type"])),
314318
resource.TestCheckResourceAttrSet("stackit_dns_record_set.record_set", "fqdn"),
315319
resource.TestCheckResourceAttrSet("stackit_dns_record_set.record_set", "state"),
320+
321+
resource.TestCheckResourceAttr("stackit_observability_instance.observability_instance", "name", testutil.ConvertConfigVariable(testConfigVarsMax["observability_instance_name"])),
322+
resource.TestCheckResourceAttr("stackit_observability_instance.observability_instance", "plan_name", testutil.ConvertConfigVariable(testConfigVarsMax["observability_plan_name"])),
316323
),
317324
},
318325
// Data sources
@@ -404,6 +411,9 @@ func TestAccDnsMaxResource(t *testing.T) {
404411
resource.TestCheckResourceAttr("data.stackit_dns_record_set.record_set", "comment", testutil.ConvertConfigVariable(testConfigVarsMax["record_comment"])),
405412
resource.TestCheckResourceAttr("data.stackit_dns_record_set.record_set", "ttl", testutil.ConvertConfigVariable(testConfigVarsMax["record_ttl"])),
406413
resource.TestCheckResourceAttr("data.stackit_dns_record_set.record_set", "type", testutil.ConvertConfigVariable(testConfigVarsMax["record_type"])),
414+
415+
resource.TestCheckResourceAttr("stackit_observability_instance.observability_instance", "name", testutil.ConvertConfigVariable(testConfigVarsMax["observability_instance_name"])),
416+
resource.TestCheckResourceAttr("stackit_observability_instance.observability_instance", "plan_name", testutil.ConvertConfigVariable(testConfigVarsMax["observability_plan_name"])),
407417
),
408418
},
409419
// Import
@@ -492,7 +502,11 @@ func TestAccDnsMaxResource(t *testing.T) {
492502
resource.TestCheckResourceAttr("stackit_dns_record_set.record_set", "ttl", testutil.ConvertConfigVariable(testConfigVarsMax["record_ttl"])),
493503
resource.TestCheckResourceAttr("stackit_dns_record_set.record_set", "type", testutil.ConvertConfigVariable(testConfigVarsMax["record_type"])),
494504
resource.TestCheckResourceAttrSet("stackit_dns_record_set.record_set", "fqdn"),
495-
resource.TestCheckResourceAttrSet("stackit_dns_record_set.record_set", "state")),
505+
resource.TestCheckResourceAttrSet("stackit_dns_record_set.record_set", "state"),
506+
507+
resource.TestCheckResourceAttr("stackit_observability_instance.observability_instance", "name", testutil.ConvertConfigVariable(testConfigVarsMax["observability_instance_name"])),
508+
resource.TestCheckResourceAttr("stackit_observability_instance.observability_instance", "plan_name", testutil.ConvertConfigVariable(testConfigVarsMax["observability_plan_name"])),
509+
),
496510
},
497511
// Deletion is done by the framework implicitly
498512
},

stackit/internal/services/dns/testdata/resource-max.tf

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ variable "contact_email" {}
77
variable "default_ttl" {}
88
variable "description" {}
99
variable "expire_time" {}
10+
variable "ext_observability_instance_id" {}
1011
variable "is_reverse_zone" {}
1112
# variable "negative_cache" {}
1213
variable "primaries" {}
@@ -21,19 +22,31 @@ variable "record_comment" {}
2122
variable "record_ttl" {}
2223
variable "record_type" {}
2324

25+
variable "observability_instance_name" {}
26+
variable "observability_plan_name" {}
2427

2528

29+
resource "stackit_observability_instance" "observability_instance" {
30+
project_id = var.project_id
31+
name = var.observability_instance_name
32+
plan_name = var.observability_plan_name
33+
}
2634

2735
resource "stackit_dns_zone" "zone" {
28-
project_id = var.project_id
29-
name = var.name
30-
dns_name = var.dns_name
31-
acl = var.acl
32-
active = var.active
33-
contact_email = var.contact_email
34-
default_ttl = var.default_ttl
35-
description = var.description
36-
expire_time = var.expire_time
36+
project_id = var.project_id
37+
name = var.name
38+
dns_name = var.dns_name
39+
acl = var.acl
40+
active = var.active
41+
contact_email = var.contact_email
42+
default_ttl = var.default_ttl
43+
description = var.description
44+
expire_time = var.expire_time
45+
extensions = {
46+
observability = {
47+
observability_instance_id = stackit_observability_instance.observability_instance.instance_id
48+
}
49+
}
3750
is_reverse_zone = var.is_reverse_zone
3851
# negative_cache = var.negative_cache
3952
primaries = var.primaries

stackit/internal/services/dns/zone/datasource.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,26 @@ func (d *zoneDataSource) Schema(ctx context.Context, _ datasource.SchemaRequest,
139139
Description: "Expire time.",
140140
Computed: true,
141141
},
142+
"extensions": schema.SingleNestedAttribute{
143+
Description: "A single extensions block as defined below.",
144+
Computed: true,
145+
Attributes: map[string]schema.Attribute{
146+
"observability": schema.SingleNestedAttribute{
147+
Description: "A single observability block as defined below.",
148+
Computed: true,
149+
Attributes: map[string]schema.Attribute{
150+
"observability_instance_id": schema.StringAttribute{
151+
Description: "Observability instance ID to choose which Observability instance is used.",
152+
Computed: true,
153+
},
154+
"state": schema.StringAttribute{
155+
Description: "State of the observability extension.",
156+
Computed: true,
157+
},
158+
},
159+
},
160+
},
161+
},
142162
"is_reverse_zone": schema.BoolAttribute{
143163
Description: "Specifies, if the zone is a reverse zone or not.",
144164
Computed: true,

stackit/internal/services/dns/zone/resource.go

Lines changed: 30 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
1818
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
1919
"github.com/hashicorp/terraform-plugin-framework/attr"
20-
"github.com/hashicorp/terraform-plugin-framework/diag"
2120
"github.com/hashicorp/terraform-plugin-framework/resource"
2221
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
2322
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
@@ -98,8 +97,9 @@ type extensions struct {
9897

9998
// Struct corresponding to extensions.Observability
10099
type observability struct {
101-
instanceId types.String `tfsdk:"instance_id"`
102-
State types.String `tfsdk:"state"`
100+
InstanceId types.String `tfsdk:"observability_instance_id"`
101+
// State is response only
102+
State types.String `tfsdk:"state"`
103103
}
104104

105105
// Types corresponding to extensions
@@ -109,8 +109,8 @@ var extensionsTypes = map[string]attr.Type{
109109

110110
// Types corresponding to observability
111111
var observabilityTypes = map[string]attr.Type{
112-
"instance_id": basetypes.StringType{},
113-
"state": basetypes.StringType{},
112+
"observability_instance_id": basetypes.StringType{},
113+
"state": basetypes.StringType{},
114114
}
115115

116116
// Metadata returns the resource type name.
@@ -249,14 +249,14 @@ func (r *zoneResource) Schema(ctx context.Context, _ resource.SchemaRequest, res
249249
Description: "A single observability block as defined below.",
250250
Optional: true,
251251
Attributes: map[string]schema.Attribute{
252-
"instance_id": schema.StringAttribute{
252+
"observability_instance_id": schema.StringAttribute{
253253
Description: "Observability instance ID to choose which Observability instance is used.",
254-
Optional: true,
255-
},
256-
"state": schema.BoolAttribute{
257-
Description: "Flag to enable/disable Observability extensions.",
258254
Required: true,
259255
},
256+
"state": schema.StringAttribute{
257+
Description: "State of the extension.",
258+
Computed: true,
259+
},
260260
},
261261
},
262262
},
@@ -605,43 +605,35 @@ func (r *zoneResource) ImportState(ctx context.Context, req resource.ImportState
605605

606606
tflog.Info(ctx, "DNS zone state imported")
607607
}
608-
func mapExtensions(ctx context.Context, z *dns.ZoneResponse, m *Model) error {
608+
609+
func mapExtensions(z *dns.ZoneResponse, m *Model) error {
609610
if z.Zone.Extensions == nil {
610-
m.Extensions = basetypes.NewObjectNull(extensionsTypes)
611+
m.Extensions = types.ObjectNull(extensionsTypes)
611612
return nil
612613
}
613614

614-
var diags diag.Diagnostics
615-
ex := extensions{}
616-
if !m.Extensions.IsNull() {
617-
diags := m.Extensions.As(ctx, &ex, basetypes.ObjectAsOptions{})
618-
if diags.HasError() {
619-
return fmt.Errorf("converting extensions object: %v", diags.Errors())
620-
}
621-
}
622-
var observabilityExtension = types.ObjectNull(observabilityTypes)
623615
if z.Zone.Extensions.ObservabilityExtension != nil {
624616
state := types.StringValue(*z.Zone.Extensions.ObservabilityExtension.State)
625617
observabilityInstanceId := types.StringValue(z.Zone.Extensions.ObservabilityExtension.ObservabilityInstanceId)
626618

627619
observabilityValues := map[string]attr.Value{
628-
"state": state,
629-
"instance_id": observabilityInstanceId,
620+
"state": state,
621+
"observability_instance_id": observabilityInstanceId,
630622
}
631-
observabilityExtension, diags = types.ObjectValue(observabilityTypes, observabilityValues)
623+
observabilityExtension, diags := types.ObjectValue(observabilityTypes, observabilityValues)
632624
if diags.HasError() {
633625
return fmt.Errorf("creating observability extension: %w", core.DiagsToError(diags))
634626
}
635-
}
636-
extensionValue := map[string]attr.Value{
637-
"observability": observabilityExtension,
638-
}
627+
extensionValue := map[string]attr.Value{
628+
"observability": observabilityExtension,
629+
}
639630

640-
extensions, diags := types.ObjectValue(extensionsTypes, extensionValue)
641-
if diags.HasError() {
642-
return fmt.Errorf("creating extensions: %w", core.DiagsToError(diags))
631+
extensions, diags := types.ObjectValue(extensionsTypes, extensionValue)
632+
if diags.HasError() {
633+
return fmt.Errorf("creating extensions: %w", core.DiagsToError(diags))
634+
}
635+
m.Extensions = extensions
643636
}
644-
m.Extensions = extensions
645637
return nil
646638
}
647639

@@ -711,7 +703,7 @@ func mapFields(ctx context.Context, zoneResp *dns.ZoneResponse, model *Model) er
711703
model.Type = types.StringValue(string(z.Type))
712704
model.Visibility = types.StringValue(string(z.Visibility))
713705

714-
return mapExtensions(ctx, zoneResp, model)
706+
return mapExtensions(zoneResp, model)
715707
}
716708

717709
func toCreatePayload(ctx context.Context, model *Model) (*dns.CreateZonePayload, error) {
@@ -730,7 +722,7 @@ func toCreatePayload(ctx context.Context, model *Model) (*dns.CreateZonePayload,
730722

731723
ext, err := getExtensionsPayload(ctx, model)
732724
if err != nil {
733-
return nil, fmt.Errorf("could not map extensions to create payload: %v", err)
725+
return nil, fmt.Errorf("could not map extensions to create payload: %w", err)
734726
}
735727
return &dns.CreateZonePayload{
736728
Name: model.Name.ValueString(),
@@ -757,7 +749,7 @@ func toUpdatePayload(ctx context.Context, model *Model) (*dns.PartialUpdateZoneP
757749

758750
ext, err := getExtensionsPayload(ctx, model)
759751
if err != nil {
760-
return nil, fmt.Errorf("could not map extensions to update payload: %v", err)
752+
return nil, fmt.Errorf("could not map extensions to update payload: %w", err)
761753
}
762754
return &dns.PartialUpdateZonePayload{
763755
Name: conversion.StringValueToPointer(model.Name),
@@ -777,7 +769,7 @@ func toUpdatePayload(ctx context.Context, model *Model) (*dns.PartialUpdateZoneP
777769
// getExtensionsPayload reads the extensions from the model and maps it to [dns.ZoneExtensions].
778770
// Returns nil if the model does not provide any extensions.
779771
func getExtensionsPayload(ctx context.Context, model *Model) (*dns.ZoneExtensions, error) {
780-
if !model.Extensions.IsNull() {
772+
if !utils.IsUndefined(model.Extensions) {
781773
ex := extensions{}
782774
diags := model.Extensions.As(ctx, &ex, basetypes.ObjectAsOptions{})
783775
if diags.HasError() {
@@ -789,14 +781,12 @@ func getExtensionsPayload(ctx context.Context, model *Model) (*dns.ZoneExtension
789781
if diags.HasError() {
790782
return nil, fmt.Errorf("failed to map extensions to domain type: %v", diags.Errors())
791783
}
792-
observabilityExtension := dns.NewZoneObservabilityExtension(obs.instanceId.ValueString())
793-
observabilityExtension.SetState(obs.State.ValueString())
784+
observabilityExtension := dns.NewZoneObservabilityExtension(obs.InstanceId.ValueString())
794785
return &dns.ZoneExtensions{
795786
ObservabilityExtension: observabilityExtension,
796787
}, nil
797-
} else {
798-
return &dns.ZoneExtensions{}, nil
799788
}
789+
return &dns.ZoneExtensions{}, nil
800790
}
801791
return nil, nil
802792
}

0 commit comments

Comments
 (0)