github.com/akamai/AkamaiOPEN-edgegrid-golang/v2@v2.17.0/pkg/configdns/record.go (about)

     1  package dns
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	"net"
     9  	"sync"
    10  )
    11  
    12  // The record types implemented and their fields are as defined here
    13  // https://developer.akamai.com/api/luna/config-dns/data.html
    14  
    15  // Records contains operations available on a Record resource
    16  // See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html
    17  type Records interface {
    18  	// RecordToMap returns a map containing record content
    19  	RecordToMap(context.Context, *RecordBody) map[string]interface{}
    20  	// Return bare bones tsig key struct
    21  	NewRecordBody(context.Context, RecordBody) *RecordBody
    22  	//  GetRecordList retrieves recordset list based on type
    23  	// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getzonerecordsets
    24  	GetRecordList(context.Context, string, string, string) (*RecordSetResponse, error)
    25  	// GetRdata retrieves record rdata, e.g. target
    26  	GetRdata(context.Context, string, string, string) ([]string, error)
    27  	// ProcessRdata
    28  	ProcessRdata(context.Context, []string, string) []string
    29  	// ParseRData parses rdata. returning map
    30  	ParseRData(context.Context, string, []string) map[string]interface{}
    31  	// GetRecord retrieves a recordset and returns as RecordBody
    32  	// See:  https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getzonerecordset
    33  	GetRecord(context.Context, string, string, string) (*RecordBody, error)
    34  	// CreateRecord creates recordset
    35  	// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#postzonerecordset
    36  	CreateRecord(context.Context, *RecordBody, string, ...bool) error
    37  	// DeleteRecord removes recordset
    38  	// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#deletezonerecordset
    39  	DeleteRecord(context.Context, *RecordBody, string, ...bool) error
    40  	// UpdateRecord replaces the recordset
    41  	// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#putzonerecordset
    42  	UpdateRecord(context.Context, *RecordBody, string, ...bool) error
    43  	// FullIPv6 is utility method to convert IP to string
    44  	FullIPv6(context.Context, net.IP) string
    45  	// PadCoordinates is utility method to convert IP to normalize coordinates
    46  	PadCoordinates(context.Context, string) string
    47  }
    48  
    49  // RecordBody contains request body for dns record
    50  type RecordBody struct {
    51  	Name       string `json:"name,omitempty"`
    52  	RecordType string `json:"type,omitempty"`
    53  	TTL        int    `json:"ttl,omitempty"`
    54  	// Active field no longer used in v2
    55  	Active bool     `json:"active,omitempty"`
    56  	Target []string `json:"rdata,omitempty"`
    57  }
    58  
    59  var (
    60  	zoneRecordWriteLock sync.Mutex
    61  )
    62  
    63  // Validate validates RecordBody
    64  func (rec *RecordBody) Validate() error {
    65  
    66  	if len(rec.Name) < 1 {
    67  		return fmt.Errorf("Record body is missing Name")
    68  	}
    69  	if len(rec.RecordType) < 1 {
    70  		return fmt.Errorf("Record body is missing RecordType")
    71  	}
    72  	if rec.TTL == 0 {
    73  		return fmt.Errorf("Record body is missing TTL")
    74  	}
    75  	if rec.Target == nil || len(rec.Target) < 1 {
    76  		return fmt.Errorf("Record body is missing Target")
    77  	}
    78  
    79  	return nil
    80  }
    81  
    82  func (p *dns) RecordToMap(ctx context.Context, record *RecordBody) map[string]interface{} {
    83  
    84  	logger := p.Log(ctx)
    85  	logger.Debug("RecordToMap")
    86  
    87  	if err := record.Validate(); err != nil {
    88  		logger.Errorf("Record to map failed. %w", err)
    89  		return nil
    90  	}
    91  
    92  	return map[string]interface{}{
    93  		"name":       record.Name,
    94  		"ttl":        record.TTL,
    95  		"recordtype": record.RecordType,
    96  		// active no longer used
    97  		"active": record.Active,
    98  		"target": record.Target,
    99  	}
   100  }
   101  
   102  func (p *dns) NewRecordBody(ctx context.Context, params RecordBody) *RecordBody {
   103  
   104  	logger := p.Log(ctx)
   105  	logger.Debug("NewRecordBody")
   106  
   107  	recordbody := &RecordBody{Name: params.Name}
   108  	return recordbody
   109  }
   110  
   111  // Eval option lock arg passed into writable endpoints. Default is true, e.g. lock
   112  func localLock(lockArg []bool) bool {
   113  
   114  	for _, lock := range lockArg {
   115  		// should only be one entry
   116  		return lock
   117  	}
   118  
   119  	return true
   120  
   121  }
   122  
   123  func (p *dns) CreateRecord(ctx context.Context, record *RecordBody, zone string, recLock ...bool) error {
   124  	// This lock will restrict the concurrency of API calls
   125  	// to 1 save request at a time. This is needed for the Soa.Serial value which
   126  	// is required to be incremented for every subsequent update to a zone
   127  	// so we have to save just one request at a time to ensure this is always
   128  	// incremented properly
   129  
   130  	if localLock(recLock) {
   131  		zoneRecordWriteLock.Lock()
   132  		defer zoneRecordWriteLock.Unlock()
   133  	}
   134  
   135  	logger := p.Log(ctx)
   136  	logger.Debug("CreateRecord")
   137  	logger.Debugf("DNS Lib Create Record: [%v]", record)
   138  	if err := record.Validate(); err != nil {
   139  		logger.Errorf("Record content not valid: %w", err)
   140  		return fmt.Errorf("Record content not valid. [%w]", err)
   141  	}
   142  
   143  	reqbody, err := convertStructToReqBody(record)
   144  	if err != nil {
   145  		return fmt.Errorf("failed to generate request body: %w", err)
   146  	}
   147  
   148  	var rec RecordBody
   149  	postURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", zone, record.Name, record.RecordType)
   150  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqbody)
   151  	if err != nil {
   152  		return fmt.Errorf("failed to create CreateRecord request: %w", err)
   153  	}
   154  
   155  	resp, err := p.Exec(req, &rec)
   156  	if err != nil {
   157  		return fmt.Errorf("CreateRecord request failed: %w", err)
   158  	}
   159  
   160  	if resp.StatusCode != http.StatusCreated {
   161  		return p.Error(resp)
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  func (p *dns) UpdateRecord(ctx context.Context, record *RecordBody, zone string, recLock ...bool) error {
   168  	// This lock will restrict the concurrency of API calls
   169  	// to 1 save request at a time. This is needed for the Soa.Serial value which
   170  	// is required to be incremented for every subsequent update to a zone
   171  	// so we have to save just one request at a time to ensure this is always
   172  	// incremented properly
   173  
   174  	if localLock(recLock) {
   175  		zoneRecordWriteLock.Lock()
   176  		defer zoneRecordWriteLock.Unlock()
   177  	}
   178  
   179  	logger := p.Log(ctx)
   180  	logger.Debug("UpdateRecord")
   181  	logger.Debugf("DNS Lib Update Record: [%v]", record)
   182  	if err := record.Validate(); err != nil {
   183  		logger.Errorf("Record content not valid: %s", err.Error())
   184  		return fmt.Errorf("Record content not valid. [%w]", err)
   185  	}
   186  
   187  	reqbody, err := convertStructToReqBody(record)
   188  	if err != nil {
   189  		return fmt.Errorf("failed to generate request body: %w", err)
   190  	}
   191  
   192  	var rec RecordBody
   193  	putURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", zone, record.Name, record.RecordType)
   194  	req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, reqbody)
   195  	if err != nil {
   196  		return fmt.Errorf("failed to create UpdateRecord request: %w", err)
   197  	}
   198  
   199  	resp, err := p.Exec(req, &rec)
   200  	if err != nil {
   201  		return fmt.Errorf("UpdateRecord request failed: %w", err)
   202  	}
   203  
   204  	if resp.StatusCode != http.StatusOK {
   205  		return p.Error(resp)
   206  	}
   207  
   208  	return nil
   209  }
   210  
   211  func (p *dns) DeleteRecord(ctx context.Context, record *RecordBody, zone string, recLock ...bool) error {
   212  	// This lock will restrict the concurrency of API calls
   213  	// to 1 save request at a time. This is needed for the Soa.Serial value which
   214  	// is required to be incremented for every subsequent update to a zone
   215  	// so we have to save just one request at a time to ensure this is always
   216  	// incremented properly
   217  
   218  	if localLock(recLock) {
   219  		zoneRecordWriteLock.Lock()
   220  		defer zoneRecordWriteLock.Unlock()
   221  	}
   222  
   223  	logger := p.Log(ctx)
   224  	logger.Debug("DeleteRecord")
   225  
   226  	if err := record.Validate(); err != nil {
   227  		logger.Errorf("Record content not valid: %w", err)
   228  		return fmt.Errorf("Record content not valid. [%w]", err)
   229  	}
   230  
   231  	//var mtbody string
   232  	deleteURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types/%s", zone, record.Name, record.RecordType)
   233  	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, deleteURL, nil)
   234  	if err != nil {
   235  		return fmt.Errorf("failed to create DeleteRecord request: %w", err)
   236  	}
   237  
   238  	resp, err := p.Exec(req, nil) //, &mtbody)
   239  	if err != nil {
   240  		return fmt.Errorf("DeleteRecord request failed: %w", err)
   241  	}
   242  
   243  	if resp.StatusCode != http.StatusNoContent {
   244  		return p.Error(resp)
   245  	}
   246  
   247  	return nil
   248  }