Skip to content
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

feat: implement name server resource for domains #669

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ export OVH_CLOUD_PROJECT_FAILOVER_IP_ROUTED_TO_1_TEST="..."
export OVH_CLOUD_PROJECT_FAILOVER_IP_ROUTED_TO_2_TEST="..."
export OVH_VRACK_SERVICE_TEST="..."
export OVH_ZONE_TEST="..."
export OVH_DOMAIN_TEST="..."

$ make testacc
```
Expand Down
1 change: 1 addition & 0 deletions ovh/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ func Provider() *schema.Provider {
"ovh_dedicated_server_reboot_task": resourceDedicatedServerRebootTask(),
"ovh_dedicated_server_update": resourceDedicatedServerUpdate(),
"ovh_dedicated_server_networking": resourceDedicatedServerNetworking(),
"ovh_domain_name_servers": resourceOvhDomainNameServers(),
"ovh_domain_zone": resourceDomainZone(),
"ovh_domain_zone_record": resourceOvhDomainZoneRecord(),
"ovh_domain_zone_redirection": resourceOvhDomainZoneRedirection(),
Expand Down
1 change: 1 addition & 0 deletions ovh/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ func testAccPreCheckOrderCloudProject(t *testing.T) {
func testAccPreCheckDomain(t *testing.T) {
testAccPreCheckCredentials(t)
checkEnvOrSkip(t, "OVH_ZONE_TEST")
checkEnvOrSkip(t, "OVH_DOMAIN_TEST")
}

// Checks that the environment variables needed to order /domain for acceptance tests
Expand Down
220 changes: 220 additions & 0 deletions ovh/resource_domain_name_servers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package ovh

import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/ovh/go-ovh/ovh"
"log"
"strings"
)

type OvhDomainNameServerUpdate struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you place the structs in a file type_domain.go, to keep consistency with other resources ?

NameServers []OvhDomainNameServer `json:"nameServers,omitempty"`
}

type OvhDomainNameServerUpdateResult struct {
CanAccelerate bool `json:"canAccelerate,omitempty"`
CanCancel bool `json:"canCancel,omitempty"`
CanRelaunch bool `json:"canRelaunch,omitempty"`
Comment string `json:"comment,omitempty"`
CreationDate string `json:"creationDate,omitempty"`
Domain string `json:"domain,omitempty"`
DoneDate string `json:"doneDate,omitempty"`
Function string `json:"function,omitempty"`
Id int64 `json:"id,omitempty"`
LastUpdate string `json:"lastUpdate,omitempty"`
Status string `json:"status,omitempty"`
TodoDate string `json:"todoDate,omitempty"`
}

type OvhDomainNameServer struct {
Id int64 `json:"id,omitempty"`
Host string `json:"host,omitempty"`
Ip string `json:"ip,omitempty"`
IsUsed bool `json:"isUsed,omitempty"`
ToDelete bool `json:"toDelete,omitempty"`
}

type OvhDomainNameServers struct {
Id string `json:"id,omitempty"`
Domain string `json:"domain,omitempty"`
Servers []OvhDomainNameServer `json:"servers,omitempty"`
}

func (r *OvhDomainNameServers) String() string {
domains := make([]string, 0)
for _, server := range r.Servers {
domains = append(domains, fmt.Sprintf(
"server[id: %v, host: %s, ip: %s, isUsed: %v, toDelete: %v]",
server.Id,
server.Host,
server.Ip,
server.IsUsed,
server.ToDelete,
))
}
return fmt.Sprintf(
"nameservers[id: %v, domain: %s, servers: [%s]]",
r.Id,
r.Domain,
strings.Join(domains, ", "),
)
}

func resourceOvhDomainNameServersImportState(
d *schema.ResourceData,
meta interface{}) ([]*schema.ResourceData, error) {
givenId := d.Id()
d.SetId(givenId)
d.Set("domain", d.Id())
results := make([]*schema.ResourceData, 1)
results[0] = d
return results, nil
}

func resourceOvhDomainNameServers() *schema.Resource {
return &schema.Resource{
Create: resourceOvhDomainNameServersCreate,
Read: resourceOvhDomainNameServersRead,
Update: resourceOvhDomainNameServersCreate, // Update is the same as create
Delete: resourceOvhDomainNameServersDelete,
Importer: &schema.ResourceImporter{
State: resourceOvhDomainNameServersImportState,
},

Schema: map[string]*schema.Schema{
"domain": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"servers": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"host": {
Type: schema.TypeString,
Required: true,
rbeuque74 marked this conversation as resolved.
Show resolved Hide resolved
},
"ip": {
Type: schema.TypeString,
Optional: true,
Default: "",
},
},
},
MinItems: 1,
},
},
}
}

func resourceOvhDomainNameServersCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)
serviceName := d.Get("domain").(string)

// Servers to put
servers := &OvhDomainNameServerUpdate{
NameServers: make([]OvhDomainNameServer, 0),
}
// Loop due to rename of the field in the API
for _, server := range d.Get("servers").([]interface{}) {
s := server.(map[string]interface{})
servers.NameServers = append(servers.NameServers, OvhDomainNameServer{
Host: s["host"].(string),
Ip: s["ip"].(string),
})
}
log.Printf("[DEBUG] OVH Record create configuration: %#v", servers)
err := config.OVHClient.Post(fmt.Sprintf("/domain/%s/nameServers/update", serviceName), servers, nil)
PhilippeVienne marked this conversation as resolved.
Show resolved Hide resolved

if err != nil {
return fmt.Errorf("failed to register OVH Nameservers: %s", err)
}

d.SetId(serviceName)

return resourceOvhDomainNameServersRead(d, meta)
}

func resourceOvhDomainNameServersRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

record, err := ovhDomainNameServers(config.OVHClient, d)

if err != nil {
return err
}

if record == nil {
return fmt.Errorf("domain %v has been deleted", d.Id())
}

d.SetId(record.Id)
d.Set("domain", record.Domain)
d.Set("servers", flattenOvhDomainNameServers(record.Servers))

return nil
}

func flattenOvhDomainNameServers(servers []OvhDomainNameServer) []interface{} {
result := make([]interface{}, 0)
for _, server := range servers {
result = append(result, map[string]interface{}{
"host": server.Host,
"ip": server.Ip,
})
}
return result
}

func resourceOvhDomainNameServersDelete(d *schema.ResourceData, meta interface{}) error {
// Note that NameServers can not be deleted, only updated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when deleting, shouldn't we call /domain/xxx/nameServers/update with an empty array ? If this doesn't work, you should call DELETE /domain/{serviceName}/nameServer/{id} on each nameServer to clean them.

d.SetId("")
return nil
}

func ovhDomainNameServers(client *ovh.Client, d *schema.ResourceData) (*OvhDomainNameServers, error) {
domain := d.Get("domain").(string)
nameServers := &OvhDomainNameServers{
Servers: make([]OvhDomainNameServer, 0),
Domain: domain,
Id: domain,
}
rec := &([]int{})

endpoint := fmt.Sprintf("/domain/%s/nameServer", domain)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please url.PathEscape() all string path parameters


err := client.Get(endpoint, rec)

if err != nil {
if err.(*ovh.APIError).Code == 404 {
return nil, nil
}
return nil, err
}

// Read each name server
for _, id := range *rec {
server, err := ovhDomainNameServer(client, domain, id)
if err != nil {
return nil, err
}
nameServers.Servers = append(nameServers.Servers, *server)
}

return nameServers, nil
}

func ovhDomainNameServer(client *ovh.Client, domain string, id int) (*OvhDomainNameServer, error) {
rec := &OvhDomainNameServer{}

endpoint := fmt.Sprintf("/domain/%s/nameServer/%d", domain, id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment, to url.PathEscape()

err := client.Get(endpoint, rec)
if err != nil {
return nil, err
}

return rec, nil
}
84 changes: 84 additions & 0 deletions ovh/resource_domain_name_servers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package ovh

import (
"fmt"
"github.com/ovh/go-ovh/ovh"
"log"
"os"
"testing"

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

func init() {
resource.AddTestSweepers("ovh_domain_name_servers", &resource.Sweeper{
Name: "ovh_domain_name_servers",
F: testSweepOvhDomainNameServers,
})
}

func testSweepOvhDomainNameServers(region string) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this func seems useless as it doesn't remove anything, or am I missing something ?

client, err := sharedClientForRegion(region)
if err != nil {
return fmt.Errorf("error getting client: %s", err)
}

domain := os.Getenv("OVH_DOMAIN_TEST")
if domain == "" {
log.Print("[DEBUG] OVH_DOMAIN_TEST is not set. No domain to sweep")
return nil
}

// Check we have access to the domain
err = client.Get(fmt.Sprintf("/domain/%s", domain), nil)

if err != nil {
if err.(*ovh.APIError).Code == 404 {
log.Printf("[DEBUG] OVH domain %s does not exist. No domain to sweep", domain)
return nil
}
return fmt.Errorf("error getting domain: %s", err)
}

return nil
}

func TestAccDomainNameServers_Basic(t *testing.T) {
domain := os.Getenv("OVH_DOMAIN_TEST")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheckDomain(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
// provider shall send an error if the TTL is less than 60
{
Config: testAccCheckOvhDomainNameServersConfig(domain, []string{"dns104.ovh.net", "ns104.ovh.net"}),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"ovh_domain_name_servers.foobar", "domain", domain),
),
},
},
})
}

func testAccCheckOvhDomainNameServersConfig(domain string, nameServers []string) string {
return `
resource "ovh_domain_name_servers" "foobar" {
domain = "` + domain + `"
` + testAccCheckOvhDomainNameServersConfigNameServers(nameServers) + `
}
`
}

func testAccCheckOvhDomainNameServersConfigNameServers(nameServers []string) string {
var config string
for _, nameServer := range nameServers {
config += `
servers {
host = "` + nameServer + `"
}
`
}
return config
}