wrapper.go raw

   1  package sakuracloud
   2  
   3  import (
   4  	"context"
   5  	"fmt"
   6  	"sync"
   7  
   8  	"github.com/go-acme/lego/v4/challenge/dns01"
   9  	"github.com/sacloud/iaas-api-go"
  10  	"github.com/sacloud/iaas-api-go/search"
  11  )
  12  
  13  // This mutex is required for concurrent updates.
  14  // see: https://github.com/go-acme/lego/pull/850
  15  var mu sync.Mutex
  16  
  17  func (d *DNSProvider) addTXTRecord(ctx context.Context, fqdn, value string, ttl int) error {
  18  	mu.Lock()
  19  	defer mu.Unlock()
  20  
  21  	zone, err := d.getHostedZone(ctx, fqdn)
  22  	if err != nil {
  23  		return err
  24  	}
  25  
  26  	subDomain, err := dns01.ExtractSubDomain(fqdn, zone.Name)
  27  	if err != nil {
  28  		return err
  29  	}
  30  
  31  	records := append(zone.Records, &iaas.DNSRecord{
  32  		Name:  subDomain,
  33  		Type:  "TXT",
  34  		RData: value,
  35  		TTL:   ttl,
  36  	})
  37  
  38  	_, err = d.client.UpdateSettings(ctx, zone.ID, &iaas.DNSUpdateSettingsRequest{
  39  		Records:      records,
  40  		SettingsHash: zone.SettingsHash,
  41  	})
  42  	if err != nil {
  43  		return fmt.Errorf("API call failed: %w", err)
  44  	}
  45  
  46  	return nil
  47  }
  48  
  49  func (d *DNSProvider) cleanupTXTRecord(ctx context.Context, fqdn, value string) error {
  50  	mu.Lock()
  51  	defer mu.Unlock()
  52  
  53  	zone, err := d.getHostedZone(ctx, fqdn)
  54  	if err != nil {
  55  		return err
  56  	}
  57  
  58  	subDomain, err := dns01.ExtractSubDomain(fqdn, zone.Name)
  59  	if err != nil {
  60  		return err
  61  	}
  62  
  63  	var updRecords iaas.DNSRecords
  64  
  65  	for _, r := range zone.Records {
  66  		if !(r.Name == subDomain && r.Type == "TXT" && r.RData == value) { //nolint:staticcheck // Clearer without De Morgan's law.
  67  			updRecords = append(updRecords, r)
  68  		}
  69  	}
  70  
  71  	settings := &iaas.DNSUpdateSettingsRequest{
  72  		Records:      updRecords,
  73  		SettingsHash: zone.SettingsHash,
  74  	}
  75  
  76  	_, err = d.client.UpdateSettings(ctx, zone.ID, settings)
  77  	if err != nil {
  78  		return fmt.Errorf("API call failed: %w", err)
  79  	}
  80  
  81  	return nil
  82  }
  83  
  84  func (d *DNSProvider) getHostedZone(ctx context.Context, domain string) (*iaas.DNS, error) {
  85  	authZone, err := dns01.FindZoneByFqdn(domain)
  86  	if err != nil {
  87  		return nil, fmt.Errorf("could not find zone: %w", err)
  88  	}
  89  
  90  	zoneName := dns01.UnFqdn(authZone)
  91  
  92  	conditions := &iaas.FindCondition{
  93  		Filter: search.Filter{
  94  			search.Key("Name"): search.ExactMatch(zoneName),
  95  		},
  96  	}
  97  
  98  	res, err := d.client.Find(ctx, conditions)
  99  	if err != nil {
 100  		if iaas.IsNotFoundError(err) {
 101  			return nil, fmt.Errorf("zone %s not found on SakuraCloud DNS: %w", zoneName, err)
 102  		}
 103  
 104  		return nil, fmt.Errorf("API call failed: %w", err)
 105  	}
 106  
 107  	for _, zone := range res.DNS {
 108  		if zone.Name == zoneName {
 109  			return zone, nil
 110  		}
 111  	}
 112  
 113  	return nil, fmt.Errorf("zone %s not found", zoneName)
 114  }
 115