domain_utils.go raw

   1  package domain
   2  
   3  import (
   4  	"time"
   5  
   6  	"github.com/scaleway/scaleway-sdk-go/errors"
   7  	"github.com/scaleway/scaleway-sdk-go/internal/async"
   8  	"github.com/scaleway/scaleway-sdk-go/scw"
   9  )
  10  
  11  const (
  12  	defaultRetryInterval = 15 * time.Second
  13  	defaultTimeout       = 5 * time.Minute
  14  )
  15  
  16  const (
  17  	// ErrCodeNoSuchDNSZone for service response error code
  18  	//
  19  	// The specified dns zone does not exist.
  20  	ErrCodeNoSuchDNSZone   = "NoSuchDNSZone"
  21  	ErrCodeNoSuchDNSRecord = "NoSuchDNSRecord"
  22  )
  23  
  24  // WaitForDNSZoneRequest is used by WaitForDNSZone method.
  25  type WaitForDNSZoneRequest struct {
  26  	DNSZone       string
  27  	DNSZones      []string
  28  	Timeout       *time.Duration
  29  	RetryInterval *time.Duration
  30  }
  31  
  32  func (s *API) WaitForDNSZone(
  33  	req *WaitForDNSZoneRequest,
  34  	opts ...scw.RequestOption,
  35  ) (*DNSZone, error) {
  36  	timeout := defaultTimeout
  37  	if req.Timeout != nil {
  38  		timeout = *req.Timeout
  39  	}
  40  	retryInterval := defaultRetryInterval
  41  	if req.RetryInterval != nil {
  42  		retryInterval = *req.RetryInterval
  43  	}
  44  
  45  	terminalStatus := map[DNSZoneStatus]struct{}{
  46  		DNSZoneStatusActive: {},
  47  		DNSZoneStatusLocked: {},
  48  		DNSZoneStatusError:  {},
  49  	}
  50  
  51  	dnsZone, err := async.WaitSync(&async.WaitSyncConfig{
  52  		Get: func() (any, bool, error) {
  53  			listReq := &ListDNSZonesRequest{
  54  				DNSZones: req.DNSZones,
  55  			}
  56  
  57  			if req.DNSZone != "" {
  58  				listReq.DNSZone = &req.DNSZone
  59  			}
  60  
  61  			// listing dnsZone zones and take the first one
  62  			DNSZones, err := s.ListDNSZones(listReq, opts...)
  63  			if err != nil {
  64  				return nil, false, err
  65  			}
  66  
  67  			if len(DNSZones.DNSZones) == 0 {
  68  				return nil, true, errors.New(ErrCodeNoSuchDNSZone)
  69  			}
  70  
  71  			zone := DNSZones.DNSZones[0]
  72  
  73  			_, isTerminal := terminalStatus[zone.Status]
  74  
  75  			return zone, isTerminal, nil
  76  		},
  77  		Timeout:          timeout,
  78  		IntervalStrategy: async.LinearIntervalStrategy(retryInterval),
  79  	})
  80  	if err != nil {
  81  		return nil, errors.Wrap(err, "waiting for DNS failed")
  82  	}
  83  
  84  	return dnsZone.(*DNSZone), nil
  85  }
  86  
  87  // WaitForDNSRecordExistRequest is used by WaitForDNSRecordExist method.
  88  type WaitForDNSRecordExistRequest struct {
  89  	DNSZone       string
  90  	RecordName    string
  91  	RecordType    RecordType
  92  	Timeout       *time.Duration
  93  	RetryInterval *time.Duration
  94  }
  95  
  96  func (s *API) WaitForDNSRecordExist(
  97  	req *WaitForDNSRecordExistRequest,
  98  	opts ...scw.RequestOption,
  99  ) (*Record, error) {
 100  	timeout := defaultTimeout
 101  	if req.Timeout != nil {
 102  		timeout = *req.Timeout
 103  	}
 104  	retryInterval := defaultRetryInterval
 105  	if req.RetryInterval != nil {
 106  		retryInterval = *req.RetryInterval
 107  	}
 108  
 109  	dns, err := async.WaitSync(&async.WaitSyncConfig{
 110  		Get: func() (any, bool, error) {
 111  			// listing dns zone records and take the first one
 112  			DNSRecords, err := s.ListDNSZoneRecords(&ListDNSZoneRecordsRequest{
 113  				Name:    req.RecordName,
 114  				Type:    req.RecordType,
 115  				DNSZone: req.DNSZone,
 116  			}, opts...)
 117  			if err != nil {
 118  				return nil, false, err
 119  			}
 120  
 121  			if DNSRecords.TotalCount == 0 {
 122  				return nil, false, errors.New(ErrCodeNoSuchDNSRecord)
 123  			}
 124  
 125  			record := DNSRecords.Records[0]
 126  
 127  			return record, true, nil
 128  		},
 129  		Timeout:          timeout,
 130  		IntervalStrategy: async.LinearIntervalStrategy(retryInterval),
 131  	})
 132  	if err != nil {
 133  		return nil, errors.Wrap(err, "check for DNS Record exist failed")
 134  	}
 135  
 136  	return dns.(*Record), nil
 137  }
 138  
 139  // WaitForOrderDomainRequest is used by WaitForOrderDomain method.
 140  type WaitForOrderDomainRequest struct {
 141  	Domain        string
 142  	Timeout       *time.Duration
 143  	RetryInterval *time.Duration
 144  }
 145  
 146  // WaitForOrderDomain waits until the domain reaches a terminal status.
 147  func (s *RegistrarAPI) WaitForOrderDomain(
 148  	req *WaitForOrderDomainRequest,
 149  	opts ...scw.RequestOption,
 150  ) (*Domain, error) {
 151  	timeout := defaultTimeout
 152  	if req.Timeout != nil {
 153  		timeout = *req.Timeout
 154  	}
 155  	retryInterval := defaultRetryInterval
 156  	if req.RetryInterval != nil {
 157  		retryInterval = *req.RetryInterval
 158  	}
 159  
 160  	// Terminal statuses indicating success or error.
 161  	terminalStatuses := map[DomainStatus]struct{}{
 162  		DomainStatusActive:      {},
 163  		DomainStatusExpired:     {},
 164  		DomainStatusLocked:      {},
 165  		DomainStatusCreateError: {},
 166  		DomainStatusRenewError:  {},
 167  		DomainStatusXferError:   {},
 168  	}
 169  
 170  	var lastStatus DomainStatus
 171  
 172  	domain, err := async.WaitSync(&async.WaitSyncConfig{
 173  		Get: func() (any, bool, error) {
 174  			resp, err := s.GetDomain(&RegistrarAPIGetDomainRequest{
 175  				Domain: req.Domain,
 176  			}, opts...)
 177  			if err != nil {
 178  				return nil, false, err
 179  			}
 180  
 181  			lastStatus = resp.Status
 182  
 183  			if _, isTerminal := terminalStatuses[resp.Status]; isTerminal {
 184  				return resp, true, nil
 185  			}
 186  			return resp, false, nil
 187  		},
 188  		Timeout:          timeout,
 189  		IntervalStrategy: async.LinearIntervalStrategy(retryInterval),
 190  	})
 191  	if err != nil {
 192  		return nil, errors.Wrap(err, "waiting for domain %s failed, last known status: %s", req.Domain, lastStatus)
 193  	}
 194  
 195  	return domain.(*Domain), nil
 196  }
 197  
 198  // WaitForAutoRenewStatusRequest defines the parameters for waiting on the auto‑renew feature.
 199  type WaitForAutoRenewStatusRequest struct {
 200  	Domain        string         // The domain to wait for.
 201  	Timeout       *time.Duration // Optional timeout.
 202  	RetryInterval *time.Duration // Optional retry interval.
 203  }
 204  
 205  // WaitForAutoRenewStatus polls the domain until its auto‑renew feature reaches a terminal state
 206  // (either "enabled" or "disabled"). It uses GetDomain() to fetch the current status.
 207  func (s *RegistrarAPI) WaitForAutoRenewStatus(req *WaitForAutoRenewStatusRequest, opts ...scw.RequestOption) (*Domain, error) {
 208  	// Use default timeout and retry interval if not provided.
 209  	timeout := defaultTimeout
 210  	if req.Timeout != nil {
 211  		timeout = *req.Timeout
 212  	}
 213  	retryInterval := defaultRetryInterval
 214  	if req.RetryInterval != nil {
 215  		retryInterval = *req.RetryInterval
 216  	}
 217  
 218  	// Terminal statuses for auto_renew: enabled or disabled.
 219  	terminalStatuses := map[DomainFeatureStatus]struct{}{
 220  		DomainFeatureStatusEnabled:  {},
 221  		DomainFeatureStatusDisabled: {},
 222  	}
 223  
 224  	var lastStatus DomainFeatureStatus
 225  
 226  	domainResult, err := async.WaitSync(&async.WaitSyncConfig{
 227  		Get: func() (any, bool, error) {
 228  			resp, err := s.GetDomain(&RegistrarAPIGetDomainRequest{
 229  				Domain: req.Domain,
 230  			}, opts...)
 231  			if err != nil {
 232  				return nil, false, err
 233  			}
 234  
 235  			lastStatus = resp.AutoRenewStatus
 236  			if _, isTerminal := terminalStatuses[resp.AutoRenewStatus]; isTerminal {
 237  				return resp, true, nil
 238  			}
 239  			return resp, false, nil
 240  		},
 241  		Timeout:          timeout,
 242  		IntervalStrategy: async.LinearIntervalStrategy(retryInterval),
 243  	})
 244  	if err != nil {
 245  		return nil, errors.Wrap(err, "waiting for auto_renew to reach a terminal state for domain %s failed, last known status: %s", req.Domain, lastStatus)
 246  	}
 247  	return domainResult.(*Domain), nil
 248  }
 249  
 250  // WaitForDNSSECStatusRequest defines the parameters for waiting on the DNSSEC feature.
 251  type WaitForDNSSECStatusRequest struct {
 252  	Domain        string         // The domain to wait for.
 253  	Timeout       *time.Duration // Optional timeout.
 254  	RetryInterval *time.Duration // Optional retry interval.
 255  }
 256  
 257  // WaitForDNSSECStatus polls the domain until its DNSSEC feature reaches a terminal state
 258  // (either "enabled" or "disabled"). It uses GetDomain() to fetch the current status.
 259  func (s *RegistrarAPI) WaitForDNSSECStatus(req *WaitForDNSSECStatusRequest, opts ...scw.RequestOption) (*Domain, error) {
 260  	// Use default timeout and retry interval if not provided.
 261  	timeout := defaultTimeout
 262  	if req.Timeout != nil {
 263  		timeout = *req.Timeout
 264  	}
 265  	retryInterval := defaultRetryInterval
 266  	if req.RetryInterval != nil {
 267  		retryInterval = *req.RetryInterval
 268  	}
 269  
 270  	// Terminal statuses for DNSSEC: enabled or disabled.
 271  	terminalStatuses := map[DomainFeatureStatus]struct{}{
 272  		DomainFeatureStatusEnabled:  {},
 273  		DomainFeatureStatusDisabled: {},
 274  	}
 275  
 276  	var lastStatus DomainFeatureStatus
 277  
 278  	domainResult, err := async.WaitSync(&async.WaitSyncConfig{
 279  		Get: func() (any, bool, error) {
 280  			// Retrieve the domain.
 281  			resp, err := s.GetDomain(&RegistrarAPIGetDomainRequest{
 282  				Domain: req.Domain,
 283  			}, opts...)
 284  			if err != nil {
 285  				return nil, false, err
 286  			}
 287  
 288  			// Check the current DNSSEC status.
 289  			lastStatus = resp.Dnssec.Status
 290  			if _, isTerminal := terminalStatuses[resp.Dnssec.Status]; isTerminal {
 291  				return resp, true, nil
 292  			}
 293  			return resp, false, nil
 294  		},
 295  		Timeout:          timeout,
 296  		IntervalStrategy: async.LinearIntervalStrategy(retryInterval),
 297  	})
 298  	if err != nil {
 299  		return nil, errors.Wrap(err, "waiting for dnssec to reach a terminal state for domain %s failed, last known status: %s", req.Domain, lastStatus)
 300  	}
 301  	return domainResult.(*Domain), nil
 302  }
 303