Skip to content

Commit 6c1b61f

Browse files
committed
feat: add ds records and name servers resources for domains
1 parent 861f0bd commit 6c1b61f

10 files changed

+989
-0
lines changed

README.md

+7
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ export OVH_CLOUD_PROJECT_FAILOVER_IP_ROUTED_TO_1_TEST="..."
147147
export OVH_CLOUD_PROJECT_FAILOVER_IP_ROUTED_TO_2_TEST="..."
148148
export OVH_VRACK_SERVICE_TEST="..."
149149
export OVH_ZONE_TEST="..."
150+
export OVH_DOMAIN_NS1_HOST_TEST="..."
151+
export OVH_DOMAIN_NS1_IP_TEST="..."
152+
export OVH_DOMAIN_NS2_HOST_TEST="..."
153+
export OVH_DOMAIN_NS3_HOST_TEST="..."
154+
export OVH_DOMAIN_DS_RECORD_ALGORITHM_TEST="..."
155+
export OVH_DOMAIN_DS_RECORD_PUBLIC_KEY_TEST="..."
156+
export OVH_DOMAIN_DS_RECORD_TAG_TEST="..."
150157

151158
$ make testacc
152159
```

ovh/domain_task.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package ovh
2+
3+
import (
4+
"fmt"
5+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
6+
"github.com/ovh/go-ovh/ovh"
7+
"log"
8+
"net/url"
9+
"time"
10+
)
11+
12+
func waitDomainTask(client *ovh.Client, domainName string, taskId int) error {
13+
endpoint := fmt.Sprintf("/domain/%s/task/%d", url.PathEscape(domainName), taskId)
14+
15+
stateConf := &retry.StateChangeConf{
16+
Pending: []string{"todo", "doing"},
17+
Target: []string{"done"},
18+
Refresh: func() (result interface{}, state string, err error) {
19+
var task DomainTask
20+
21+
if err := client.Get(endpoint, &task); err != nil {
22+
log.Printf("[ERROR] couldn't fetch task %d for domain %s:\n\t%s\n", taskId, domainName, err.Error())
23+
return nil, "error", err
24+
}
25+
26+
return task, task.Status, nil
27+
},
28+
Timeout: 10 * time.Minute,
29+
Delay: 30 * time.Second,
30+
MinTimeout: 5 * time.Second,
31+
}
32+
33+
_, err := stateConf.WaitForState()
34+
35+
if err != nil {
36+
return fmt.Errorf("error waiting for domain: %s task: %d to complete:\n\t%s\n", domainName, taskId, err.Error())
37+
}
38+
39+
return err
40+
}

ovh/provider.go

+2
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,8 @@ func Provider() *schema.Provider {
241241
"ovh_dedicated_server_reboot_task": resourceDedicatedServerRebootTask(),
242242
"ovh_dedicated_server_update": resourceDedicatedServerUpdate(),
243243
"ovh_dedicated_server_networking": resourceDedicatedServerNetworking(),
244+
"ovh_domain_ds_records": resourceDomainDsRecords(),
245+
"ovh_domain_name_servers": resourceDomainNameServers(),
244246
"ovh_domain_zone": resourceDomainZone(),
245247
"ovh_domain_zone_record": resourceOvhDomainZoneRecord(),
246248
"ovh_domain_zone_redirection": resourceOvhDomainZoneRedirection(),

ovh/resource_domain_ds_records.go

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package ovh
2+
3+
import (
4+
"fmt"
5+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
6+
"github.com/ovh/terraform-provider-ovh/ovh/helpers"
7+
"log"
8+
"net/url"
9+
"time"
10+
)
11+
12+
func resourceDomainDsRecords() *schema.Resource {
13+
return &schema.Resource{
14+
Description: "Resource to manage a domain name DS records",
15+
Schema: resourceDomainDsRecordsSchema(),
16+
Create: resourceDomainDsRecordsUpdate,
17+
Read: resourceDomainDsRecordsRead,
18+
Update: resourceDomainDsRecordsUpdate,
19+
Delete: resourceDomainDsRecordsDelete,
20+
Importer: &schema.ResourceImporter{
21+
State: func(resourceData *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
22+
resourceData.Set("domain", resourceData.Id())
23+
return []*schema.ResourceData{resourceData}, nil
24+
},
25+
},
26+
27+
Timeouts: &schema.ResourceTimeout{
28+
Default: schema.DefaultTimeout(10 * time.Minute),
29+
Read: schema.DefaultTimeout(30 * time.Second),
30+
Create: schema.DefaultTimeout(10 * time.Minute),
31+
Update: schema.DefaultTimeout(10 * time.Minute),
32+
Delete: schema.DefaultTimeout(10 * time.Minute),
33+
},
34+
}
35+
}
36+
37+
func resourceDomainDsRecordsSchema() map[string]*schema.Schema {
38+
resourceSchema := map[string]*schema.Schema{
39+
"domain": {
40+
Type: schema.TypeString,
41+
Description: "Domain name",
42+
Required: true,
43+
ForceNew: true,
44+
},
45+
"ds_records": {
46+
Type: schema.TypeList,
47+
Description: "DS Records for the domain",
48+
Elem: &schema.Resource{
49+
Schema: map[string]*schema.Schema{
50+
"algorithm": {
51+
Type: schema.TypeString,
52+
Description: "Algorithm name of the DNSSEC key",
53+
Required: true,
54+
ValidateFunc: helpers.ValidateEnum([]string{"RSASHA1", "RSASHA1_NSEC3_SHA1", "RSASHA256", "RSASHA512", "ECDSAP256SHA256", "ECDSAP384SHA384", "ED25519"}),
55+
},
56+
"flags": {
57+
Type: schema.TypeString,
58+
Description: "Flag name of the DNSSEC key",
59+
Required: true,
60+
ValidateFunc: helpers.ValidateEnum([]string{"ZONE_SIGNING_KEY", "KEY_SIGNING_KEY"}),
61+
},
62+
"public_key": {
63+
Type: schema.TypeString,
64+
Description: "Public key",
65+
Required: true,
66+
},
67+
"tag": {
68+
Type: schema.TypeInt,
69+
Description: "Tag of the DNSSEC key",
70+
Required: true,
71+
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
72+
if v.(int) <= 0 || v.(int) >= 65536 {
73+
errors = append(errors, fmt.Errorf(`Field "tag" must be larger than 0 and smaller than 65536.`))
74+
return
75+
}
76+
77+
return
78+
},
79+
},
80+
},
81+
},
82+
Required: true,
83+
MinItems: 1,
84+
MaxItems: 4,
85+
},
86+
}
87+
88+
return resourceSchema
89+
}
90+
91+
func resourceDomainDsRecordsRead(resourceData *schema.ResourceData, meta interface{}) error {
92+
config := meta.(*Config)
93+
domainName := resourceData.Id()
94+
95+
domainDsRecords := &DomainDsRecords{
96+
Domain: domainName,
97+
}
98+
99+
log.Printf("[DEBUG] Will read domain name DS records: %s\n", domainName)
100+
101+
responseData := &[]int{}
102+
endpoint := fmt.Sprintf("/domain/%s/dsRecord", url.PathEscape(domainName))
103+
104+
if err := config.OVHClient.Get(endpoint, &responseData); err != nil {
105+
return fmt.Errorf("calling GET %s:\n\t %s", endpoint, err.Error())
106+
}
107+
108+
for _, dsRecordId := range *responseData {
109+
responseData := &DomainDsRecord{}
110+
endpoint := fmt.Sprintf("/domain/%s/dsRecord/%d", url.PathEscape(domainName), dsRecordId)
111+
112+
if err := config.OVHClient.Get(endpoint, &responseData); err != nil {
113+
return helpers.CheckDeleted(resourceData, err, endpoint)
114+
}
115+
116+
domainDsRecords.DsRecords = append(domainDsRecords.DsRecords, *responseData)
117+
}
118+
119+
resourceData.SetId(domainName)
120+
for k, v := range domainDsRecords.ToMap() {
121+
resourceData.Set(k, v)
122+
}
123+
124+
return nil
125+
}
126+
127+
func resourceDomainDsRecordsUpdate(resourceData *schema.ResourceData, meta interface{}) error {
128+
config := meta.(*Config)
129+
domainName := resourceData.Get("domain").(string)
130+
task := &DomainTask{}
131+
132+
dsRecordsUpdate := &DomainDsRecordsUpdateOpts{
133+
DsRecords: make([]DomainDsRecord, 0),
134+
}
135+
136+
for _, dsRecord := range resourceData.Get("ds_records").([]interface{}) {
137+
record := dsRecord.(map[string]interface{})
138+
139+
dsRecordsUpdate.DsRecords = append(dsRecordsUpdate.DsRecords, DomainDsRecord{
140+
Algorithm: DsRecordAlgorithmValuesMap[record["algorithm"].(string)],
141+
Flags: DsRecordFlagValuesMap[record["flags"].(string)],
142+
PublicKey: record["public_key"].(string),
143+
Tag: record["tag"].(int),
144+
})
145+
}
146+
147+
log.Printf("[DEBUG] Will update domain name DS records: %s\n", domainName)
148+
149+
endpoint := fmt.Sprintf("/domain/%s/dsRecord", url.PathEscape(domainName))
150+
151+
if err := config.OVHClient.Post(endpoint, dsRecordsUpdate, &task); err != nil {
152+
return fmt.Errorf("calling POST %s :\n\t %s", endpoint, err.Error())
153+
}
154+
155+
if err := waitDomainTask(config.OVHClient, domainName, task.TaskId); err != nil {
156+
return fmt.Errorf("waiting for %s DS records to be updated: %s", domainName, err.Error())
157+
}
158+
159+
resourceData.SetId(domainName)
160+
161+
return resourceDomainDsRecordsRead(resourceData, meta)
162+
}
163+
164+
func resourceDomainDsRecordsDelete(resourceData *schema.ResourceData, meta interface{}) error {
165+
config := meta.(*Config)
166+
domainName := resourceData.Get("domain").(string)
167+
task := &DomainTask{}
168+
169+
domainDsRecordsUpdateOpts := &DomainDsRecordsUpdateOpts{
170+
DsRecords: make([]DomainDsRecord, 0),
171+
}
172+
173+
log.Printf("[DEBUG] Will remove all domain name DS records: %s\n", domainName)
174+
175+
endpoint := fmt.Sprintf("/domain/%s/dsRecord", url.PathEscape(domainName))
176+
177+
if err := config.OVHClient.Post(endpoint, domainDsRecordsUpdateOpts, &task); err != nil {
178+
return fmt.Errorf("calling POST %s :\n\t %s", endpoint, err.Error())
179+
}
180+
181+
if err := waitDomainTask(config.OVHClient, domainName, task.TaskId); err != nil {
182+
return fmt.Errorf("waiting for %s DS records to be deleted: %s", domainName, err.Error())
183+
}
184+
185+
resourceData.SetId("")
186+
187+
return nil
188+
}

0 commit comments

Comments
 (0)