Skip to content

feat: Add resource ovh_domain_zone_import #716

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ovh/provider_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ func (p *OvhProvider) Resources(_ context.Context) []func() resource.Resource {
NewCloudProjectGatewayInterfaceResource,
NewDbaasLogsTokenResource,
NewDomainZoneDnssecResource,
NewDomainZoneImportResource,
NewIpFirewallResource,
NewIpFirewallRuleResource,
NewIploadbalancingUdpFrontendResource,
Expand Down
165 changes: 165 additions & 0 deletions ovh/resource_domain_zone_import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package ovh

import (
"context"
"encoding/json"
"fmt"
"log"
"net/url"
"time"

"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
"github.com/ovh/terraform-provider-ovh/ovh/types"
)

var _ resource.ResourceWithConfigure = (*domainZoneImportResource)(nil)

func NewDomainZoneImportResource() resource.Resource {
return &domainZoneImportResource{}
}

type domainZoneImportResource struct {
config *Config
}

func (r *domainZoneImportResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_domain_zone_import"
}

func (d *domainZoneImportResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

config, ok := req.ProviderData.(*Config)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *Config, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)
return
}

d.config = config
}

func (d *domainZoneImportResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = DomainZoneImportResourceSchema(ctx)
}

func (r *domainZoneImportResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var (
data DomainZoneImportModel
task DomainZoneTask
export string
)

// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

// Import zone file
endpoint := "/domain/zone/" + url.PathEscape(data.ZoneName.ValueString()) + "/import"
if err := r.config.OVHClient.Post(endpoint, data.ToCreate(), &task); err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Error calling Post %s", endpoint), err.Error())
return
}

// Wait for import task completion
if err := waitDNSTask(ctx, r.config, data.ZoneName.ValueString(), task.TaskID); err != nil {
resp.Diagnostics.AddError("Error waiting for task completion", err.Error())
return
}

// Export zone file
endpoint = "/domain/zone/" + url.PathEscape(data.ZoneName.ValueString()) + "/export"
if err := r.config.OVHClient.Get(endpoint, &export); err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Error calling Get %s", endpoint), err.Error())
return
}
data.ExportedContent = types.NewTfStringValue(export)

// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *domainZoneImportResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var (
data DomainZoneImportModel
responseData string
)

// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

endpoint := "/domain/zone/" + url.PathEscape(data.ZoneName.ValueString()) + "/export"
if err := r.config.OVHClient.Get(endpoint, &responseData); err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error calling Get %s", endpoint),
err.Error(),
)
return
}

// Force diff if records have been modified
if data.ExportedContent.ValueString() != responseData {
data.ZoneFile = types.NewTfStringValue(responseData)
}

// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *domainZoneImportResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
resp.Diagnostics.AddError("update should never happen", "this code should be unreachable")
}

func (r *domainZoneImportResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data DomainZoneImportModel

// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

// Delete API call logic
endpoint := "/domain/zone/" + url.PathEscape(data.ZoneName.ValueString()) + "/reset"
if err := r.config.OVHClient.Post(endpoint, json.RawMessage(`{"minimized":false}`), nil); err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error calling Post %s", endpoint),
err.Error(),
)
}
}

func waitDNSTask(ctx context.Context, config *Config, zoneName string, taskID int) error {
endpoint := fmt.Sprintf("/domain/zone/%s/task/%d", url.PathEscape(zoneName), taskID)

stateConf := &retry.StateChangeConf{
Pending: []string{"doing", "todo"},
Target: []string{"done"},
Refresh: func() (interface{}, string, error) {
var task DomainZoneTask
if err := config.OVHClient.Get(endpoint, &task); err != nil {
log.Printf("[ERROR] couldn't fetch task %d: error: %v", taskID, err)
return nil, "error", err
}
return task.Status, task.Status, nil
},
Timeout: time.Hour,
Delay: 10 * time.Second,
MinTimeout: 3 * time.Second,
}

_, err := stateConf.WaitForStateContext(ctx)

return err
}
65 changes: 65 additions & 0 deletions ovh/resource_domain_zone_import_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 55 additions & 0 deletions ovh/resource_domain_zone_import_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package ovh

import (
"errors"
"fmt"
"os"
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func TestAccDomainZoneImport_Basic(t *testing.T) {
zone := os.Getenv("OVH_ZONE_TEST")
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`
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`

config := `
resource "ovh_domain_zone_import" "import" {
zone_name = "%s"
zone_file = "%s"
}
`

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheckDomain(t) },
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: fmt.Sprintf(config, zone, zoneFileContent),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("ovh_domain_zone_import.import", "zone_name", zone),
resource.TestCheckResourceAttrWith("ovh_domain_zone_import.import", "exported_content", func(value string) error {
if !strings.Contains(value, "hey") {
return errors.New("unexpected content in zone export")
}
return nil
}),
),
},
{
Config: fmt.Sprintf(config, zone, zoneFileContentUpdated),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("ovh_domain_zone_import.import", "zone_name", zone),
resource.TestCheckResourceAttrWith("ovh_domain_zone_import.import", "exported_content", func(value string) error {
if !strings.Contains(value, "hello") {
return errors.New("unexpected content in zone export")
}
return nil
}),
),
},
},
})
}
14 changes: 14 additions & 0 deletions ovh/types_domain_zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,17 @@ func (v DomainZone) ToMap() map[string]interface{} {
type DomainZoneConfirmTerminationOpts struct {
Token string `json:"token"`
}

type DomainZoneTask struct {
CanAccelerate bool `json:"canAccelerate"`
CanCancel bool `json:"canCancel"`
CanRelaunch bool `json:"canRelaunch"`
Comment string `json:"comment"`
CreationDate string `json:"creationDate"`
DoneDate string `json:"doneDate"`
Function string `json:"function"`
TaskID int `json:"id"`
LastUpdate string `json:"lastUpdate"`
Status string `json:"status"`
TodoDate string `json:"todoDate"`
}
27 changes: 27 additions & 0 deletions website/docs/r/domain_zone_import.html.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
subcategory : "Domain names"
---

# ovh_domain_zone_import

Handle a whole DNS zone using a zone file.

~> __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.

## Example Usage

```hcl
resource "ovh_domain_zone_import" "import" {
zone_name = "mysite.ovh"
zone_file = file("./example.zone")
}
```

## Argument Reference

* `zone_name` - (Required) The name of the domain zone
* `zone_file` - (Required) Content of the zone file to import

## Attributes Reference

* `exported_content` - Zone file exported from the API
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Provides a OVHcloud domain zone record.

!> 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.

~> __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.

## Example Usage

```hcl
Expand Down