github.com/akamai/AkamaiOPEN-edgegrid-golang/v5@v5.0.0/pkg/dns/record.go (about)

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