Skip to content

Commit c22ccde

Browse files
authored
Merge pull request #716 from ovh/dev/aamstutz/zone-import
feat: Add resource ovh_domain_zone_import
2 parents 3351440 + c261e41 commit c22ccde

9 files changed

+329
-0
lines changed

ovh/provider_new.go

+1
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ func (p *OvhProvider) Resources(_ context.Context) []func() resource.Resource {
212212
NewCloudProjectGatewayInterfaceResource,
213213
NewDbaasLogsTokenResource,
214214
NewDomainZoneDnssecResource,
215+
NewDomainZoneImportResource,
215216
NewIpFirewallResource,
216217
NewIpFirewallRuleResource,
217218
NewIploadbalancingUdpFrontendResource,

ovh/resource_domain_zone_import.go

+165
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package ovh
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"log"
8+
"net/url"
9+
"time"
10+
11+
"github.com/hashicorp/terraform-plugin-framework/resource"
12+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
13+
"github.com/ovh/terraform-provider-ovh/ovh/types"
14+
)
15+
16+
var _ resource.ResourceWithConfigure = (*domainZoneImportResource)(nil)
17+
18+
func NewDomainZoneImportResource() resource.Resource {
19+
return &domainZoneImportResource{}
20+
}
21+
22+
type domainZoneImportResource struct {
23+
config *Config
24+
}
25+
26+
func (r *domainZoneImportResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
27+
resp.TypeName = req.ProviderTypeName + "_domain_zone_import"
28+
}
29+
30+
func (d *domainZoneImportResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
31+
if req.ProviderData == nil {
32+
return
33+
}
34+
35+
config, ok := req.ProviderData.(*Config)
36+
if !ok {
37+
resp.Diagnostics.AddError(
38+
"Unexpected Resource Configure Type",
39+
fmt.Sprintf("Expected *Config, got: %T. Please report this issue to the provider developers.", req.ProviderData),
40+
)
41+
return
42+
}
43+
44+
d.config = config
45+
}
46+
47+
func (d *domainZoneImportResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
48+
resp.Schema = DomainZoneImportResourceSchema(ctx)
49+
}
50+
51+
func (r *domainZoneImportResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
52+
var (
53+
data DomainZoneImportModel
54+
task DomainZoneTask
55+
export string
56+
)
57+
58+
// Read Terraform plan data into the model
59+
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
60+
if resp.Diagnostics.HasError() {
61+
return
62+
}
63+
64+
// Import zone file
65+
endpoint := "/domain/zone/" + url.PathEscape(data.ZoneName.ValueString()) + "/import"
66+
if err := r.config.OVHClient.Post(endpoint, data.ToCreate(), &task); err != nil {
67+
resp.Diagnostics.AddError(fmt.Sprintf("Error calling Post %s", endpoint), err.Error())
68+
return
69+
}
70+
71+
// Wait for import task completion
72+
if err := waitDNSTask(ctx, r.config, data.ZoneName.ValueString(), task.TaskID); err != nil {
73+
resp.Diagnostics.AddError("Error waiting for task completion", err.Error())
74+
return
75+
}
76+
77+
// Export zone file
78+
endpoint = "/domain/zone/" + url.PathEscape(data.ZoneName.ValueString()) + "/export"
79+
if err := r.config.OVHClient.Get(endpoint, &export); err != nil {
80+
resp.Diagnostics.AddError(fmt.Sprintf("Error calling Get %s", endpoint), err.Error())
81+
return
82+
}
83+
data.ExportedContent = types.NewTfStringValue(export)
84+
85+
// Save data into Terraform state
86+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
87+
}
88+
89+
func (r *domainZoneImportResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
90+
var (
91+
data DomainZoneImportModel
92+
responseData string
93+
)
94+
95+
// Read Terraform prior state data into the model
96+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
97+
if resp.Diagnostics.HasError() {
98+
return
99+
}
100+
101+
endpoint := "/domain/zone/" + url.PathEscape(data.ZoneName.ValueString()) + "/export"
102+
if err := r.config.OVHClient.Get(endpoint, &responseData); err != nil {
103+
resp.Diagnostics.AddError(
104+
fmt.Sprintf("Error calling Get %s", endpoint),
105+
err.Error(),
106+
)
107+
return
108+
}
109+
110+
// Force diff if records have been modified
111+
if data.ExportedContent.ValueString() != responseData {
112+
data.ZoneFile = types.NewTfStringValue(responseData)
113+
}
114+
115+
// Save updated data into Terraform state
116+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
117+
}
118+
119+
func (r *domainZoneImportResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
120+
resp.Diagnostics.AddError("update should never happen", "this code should be unreachable")
121+
}
122+
123+
func (r *domainZoneImportResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
124+
var data DomainZoneImportModel
125+
126+
// Read Terraform prior state data into the model
127+
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
128+
129+
if resp.Diagnostics.HasError() {
130+
return
131+
}
132+
133+
// Delete API call logic
134+
endpoint := "/domain/zone/" + url.PathEscape(data.ZoneName.ValueString()) + "/reset"
135+
if err := r.config.OVHClient.Post(endpoint, json.RawMessage(`{"minimized":false}`), nil); err != nil {
136+
resp.Diagnostics.AddError(
137+
fmt.Sprintf("Error calling Post %s", endpoint),
138+
err.Error(),
139+
)
140+
}
141+
}
142+
143+
func waitDNSTask(ctx context.Context, config *Config, zoneName string, taskID int) error {
144+
endpoint := fmt.Sprintf("/domain/zone/%s/task/%d", url.PathEscape(zoneName), taskID)
145+
146+
stateConf := &retry.StateChangeConf{
147+
Pending: []string{"doing", "todo"},
148+
Target: []string{"done"},
149+
Refresh: func() (interface{}, string, error) {
150+
var task DomainZoneTask
151+
if err := config.OVHClient.Get(endpoint, &task); err != nil {
152+
log.Printf("[ERROR] couldn't fetch task %d: error: %v", taskID, err)
153+
return nil, "error", err
154+
}
155+
return task.Status, task.Status, nil
156+
},
157+
Timeout: time.Hour,
158+
Delay: 10 * time.Second,
159+
MinTimeout: 3 * time.Second,
160+
}
161+
162+
_, err := stateConf.WaitForStateContext(ctx)
163+
164+
return err
165+
}

ovh/resource_domain_zone_import_gen.go

+65
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package ovh
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"os"
7+
"strings"
8+
"testing"
9+
10+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
11+
)
12+
13+
func TestAccDomainZoneImport_Basic(t *testing.T) {
14+
zone := os.Getenv("OVH_ZONE_TEST")
15+
zoneFileContent := `$TTL 3600\n@\tIN SOA dns11.ovh.net. tech.ovh.net. (2024090445 86400 3600 3600000 60)\n IN NS ns11.ovh.net.\nwww IN TXT \"3|hey\"\n`
16+
zoneFileContentUpdated := `$TTL 3600\n@\tIN SOA dns11.ovh.net. tech.ovh.net. (2024090445 86400 3600 3600000 60)\n IN NS ns11.ovh.net.\nwww IN TXT \"3|hello\"\n`
17+
18+
config := `
19+
resource "ovh_domain_zone_import" "import" {
20+
zone_name = "%s"
21+
zone_file = "%s"
22+
}
23+
`
24+
25+
resource.Test(t, resource.TestCase{
26+
PreCheck: func() { testAccPreCheckDomain(t) },
27+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
28+
Steps: []resource.TestStep{
29+
{
30+
Config: fmt.Sprintf(config, zone, zoneFileContent),
31+
Check: resource.ComposeTestCheckFunc(
32+
resource.TestCheckResourceAttr("ovh_domain_zone_import.import", "zone_name", zone),
33+
resource.TestCheckResourceAttrWith("ovh_domain_zone_import.import", "exported_content", func(value string) error {
34+
if !strings.Contains(value, "hey") {
35+
return errors.New("unexpected content in zone export")
36+
}
37+
return nil
38+
}),
39+
),
40+
},
41+
{
42+
Config: fmt.Sprintf(config, zone, zoneFileContentUpdated),
43+
Check: resource.ComposeTestCheckFunc(
44+
resource.TestCheckResourceAttr("ovh_domain_zone_import.import", "zone_name", zone),
45+
resource.TestCheckResourceAttrWith("ovh_domain_zone_import.import", "exported_content", func(value string) error {
46+
if !strings.Contains(value, "hello") {
47+
return errors.New("unexpected content in zone export")
48+
}
49+
return nil
50+
}),
51+
),
52+
},
53+
},
54+
})
55+
}

ovh/types_domain_zone.go

+14
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,17 @@ func (v DomainZone) ToMap() map[string]interface{} {
2828
type DomainZoneConfirmTerminationOpts struct {
2929
Token string `json:"token"`
3030
}
31+
32+
type DomainZoneTask struct {
33+
CanAccelerate bool `json:"canAccelerate"`
34+
CanCancel bool `json:"canCancel"`
35+
CanRelaunch bool `json:"canRelaunch"`
36+
Comment string `json:"comment"`
37+
CreationDate string `json:"creationDate"`
38+
DoneDate string `json:"doneDate"`
39+
Function string `json:"function"`
40+
TaskID int `json:"id"`
41+
LastUpdate string `json:"lastUpdate"`
42+
Status string `json:"status"`
43+
TodoDate string `json:"todoDate"`
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
subcategory : "Domain names"
3+
---
4+
5+
# ovh_domain_zone_import
6+
7+
Handle a whole DNS zone using a zone file.
8+
9+
~> __WARNING__ This resource and resource `ovh_domain_zone_record` should not be used together as `ovh_domain_zone_import` controls the whole DNS zone at once.
10+
11+
## Example Usage
12+
13+
```hcl
14+
resource "ovh_domain_zone_import" "import" {
15+
zone_name = "mysite.ovh"
16+
zone_file = file("./example.zone")
17+
}
18+
```
19+
20+
## Argument Reference
21+
22+
* `zone_name` - (Required) The name of the domain zone
23+
* `zone_file` - (Required) Content of the zone file to import
24+
25+
## Attributes Reference
26+
27+
* `exported_content` - Zone file exported from the API

website/docs/r/ovh_domain_zone_record.html.markdown website/docs/r/domain_zone_record.html.markdown

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Provides a OVHcloud domain zone record.
88

99
!> The `Change in text format` feature available in the web console will update the `ovh_domain_zone_record` ids if you use it. Hence if you have created your records with terraform, you will get some `record has been deleted` errors. The workaround is to `terraform import` all the records with the updated ids and to stop to mix web console and terraform.
1010

11+
~> __WARNING__ This resource and resource `ovh_domain_zone_import` should not be used together as `ovh_domain_zone_import` controls the whole DNS zone at once.
12+
1113
## Example Usage
1214

1315
```hcl

0 commit comments

Comments
 (0)