Skip to content

Commit

Permalink
livedns: add a resource to manipulate domain keys
Browse files Browse the repository at this point in the history
There are two important shortcomings with this implementation:

 - When creating a key, the API does not return its UUID. We guess the
   UUID to be the last one, but this is quite a wild guest. Gandi
   should provide the UUID in the return of the API call.

 - go-gandi does not return the public key when asking for the key
   associated to a domain. This makes it impossible to register the
   key properly with the Domain API.
   See go-gandi/go-gandi#55

Also, I didn't generate the documentation as I expect this to be done
prior to releasing.
  • Loading branch information
vincentbernat committed Jan 8, 2022
1 parent 434e65f commit e33f849
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 0 deletions.
1 change: 1 addition & 0 deletions gandi/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ func Provider() *schema.Provider {
ResourcesMap: map[string]*schema.Resource{
"gandi_livedns_domain": resourceLiveDNSDomain(),
"gandi_livedns_record": resourceLiveDNSRecord(),
"gandi_livedns_key": resourceLiveDNSKey(),
"gandi_domain": resourceDomain(),
"gandi_mailbox": resourceMailbox(),
"gandi_email_forwarding": resourceEmailForwarding(),
Expand Down
157 changes: 157 additions & 0 deletions gandi/resource_livedns_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package gandi

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceLiveDNSKey() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"domain": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Domain name",
},
"flags": {
Type: schema.TypeInt,
Computed: true,
Description: "DNSSEC key flags",
},
"algorithm": {
Type: schema.TypeInt,
Computed: true,
Description: "DNSSEC algorithm type",
},
"algorithm_name": {
Type: schema.TypeString,
Computed: true,
Description: "DNSSEC algorithm name",
},
"deleted": {
Type: schema.TypeBool,
Computed: true,
Description: "Is the key deleted?",
},
"ds": {
Type: schema.TypeString,
Computed: true,
Description: "DS record as RFC1035 line",
},
"status": {
Type: schema.TypeString,
Computed: true,
Description: "Current status of the key",
},
},
CreateContext: resourceLiveDNSKeyCreate,
Delete: resourceLiveDNSKeyDelete,
Read: resourceLiveDNSKeyRead,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

func resourceLiveDNSKeyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client := meta.(*clients).LiveDNS
domain := d.Get("domain").(string)

// The API does not return the key UUID. Not very convenient.
// We will assume we will use the last key.
last := ""
keys, err := client.GetDomainKeys(domain)
if err != nil {
return diag.FromErr(err)
}
if len(keys) > 0 {
last = keys[len(keys)-1].UUID
}

_, err = client.SignDomain(domain)
if err != nil {
return diag.FromErr(err)
}

err = resource.RetryContext(ctx, d.Timeout(schema.TimeoutCreate), func() *resource.RetryError {
keys, err := client.GetDomainKeys(domain)
if err != nil {
return resource.NonRetryableError(fmt.Errorf("error getting keys: %s", err))
}
if len(keys) == 0 || keys[len(keys)-1].UUID == last {
return resource.RetryableError(fmt.Errorf("expected domain key not found"))
}
key := keys[len(keys)-1]
d.SetId(key.UUID)
if err := d.Set("domain", domain); err != nil {
return resource.NonRetryableError(
fmt.Errorf("failed to set domain for %s/%s: %s", domain, key.UUID, err))
}
return nil
})
if err != nil {
return diag.FromErr(err)
}

return diag.FromErr(resourceLiveDNSKeyRead(d, meta))
}

func resourceLiveDNSKeyRead(d *schema.ResourceData, meta interface{}) (err error) {
client := meta.(*clients).LiveDNS
domain := d.Get("domain").(string)
id := d.Id()
if strings.Contains(id, "/") {
parts := strings.SplitN(id, "/", 2)
domain = parts[0]
id = parts[1]
d.SetId(id)
}

key, err := client.GetDomainKey(domain, id)
if err != nil {
return
}

if err := d.Set("flags", key.Flags); err != nil {
return fmt.Errorf("failed to set flags for %s/%s: %w", domain, id, err)
}
if err := d.Set("algorithm", key.Algorithm); err != nil {
return fmt.Errorf("failed to set algorithm for %s/%s: %w", domain, id, err)
}
if err := d.Set("algorithm_name", key.AlgorithmName); err != nil {
return fmt.Errorf("failed to set algorithm name for %s/%s: %w", domain, id, err)
}
if err := d.Set("deleted", key.Deleted); err != nil {
return fmt.Errorf("failed to set deleted flag for %s/%s: %w", domain, id, err)
}
if err := d.Set("ds", key.DS); err != nil {
return fmt.Errorf("failed to set DS line for %s/%s: %w", domain, id, err)
}
if err := d.Set("status", key.Status); err != nil {
return fmt.Errorf("failed to set status for %s/%s: %w", domain, id, err)
}
return
}

func resourceLiveDNSKeyDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*clients).LiveDNS
domain := d.Get("domain").(string)
id := d.Id()
if strings.Contains(id, "/") {
parts := strings.SplitN(id, "/", 2)
domain = parts[0]
id = parts[1]
d.SetId(id)
}

if err := client.DeleteDomainKey(domain, id); err != nil {
return fmt.Errorf("failed to delete key %s/%s: %s", domain, id, err)
}
return nil
}

0 comments on commit e33f849

Please sign in to comment.