github.com/akamai/AkamaiOPEN-edgegrid-golang/v8@v8.1.0/pkg/dns/recordsets.go (about)

     1  package dns
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  
     8  	validation "github.com/go-ozzo/ozzo-validation/v4"
     9  
    10  	"strconv"
    11  	"sync"
    12  )
    13  
    14  var (
    15  	zoneRecordSetsWriteLock sync.Mutex
    16  )
    17  
    18  // Recordsets contains operations available on a record sets.
    19  type Recordsets interface {
    20  	// GetRecordSets retrieves record sets with Query Args. No formatting of arg values.
    21  	//
    22  	// See: https://techdocs.akamai.com/edge-dns/reference/get-zones-zone-recordsets
    23  	GetRecordSets(context.Context, string, ...RecordSetQueryArgs) (*RecordSetResponse, error)
    24  	// CreateRecordSets creates multiple record sets.
    25  	//
    26  	// See: https://techdocs.akamai.com/edge-dns/reference/post-zones-zone-recordsets
    27  	CreateRecordSets(context.Context, *RecordSets, string, ...bool) error
    28  	// UpdateRecordSets replaces list of record sets.
    29  	//
    30  	// See: https://techdocs.akamai.com/edge-dns/reference/put-zones-zone-recordsets
    31  	UpdateRecordSets(context.Context, *RecordSets, string, ...bool) error
    32  }
    33  
    34  // RecordSetQueryArgs contains query parameters for recordset request
    35  type RecordSetQueryArgs struct {
    36  	Page     int
    37  	PageSize int
    38  	Search   string
    39  	ShowAll  bool
    40  	SortBy   string
    41  	Types    string
    42  }
    43  
    44  // RecordSets Struct. Used for Create and Update record sets. Contains a list of RecordSet objects
    45  type RecordSets struct {
    46  	RecordSets []RecordSet `json:"recordsets"`
    47  }
    48  
    49  // RecordSet contains record set metadata
    50  type RecordSet struct {
    51  	Name  string   `json:"name"`
    52  	Type  string   `json:"type"`
    53  	TTL   int      `json:"ttl"`
    54  	Rdata []string `json:"rdata"`
    55  }
    56  
    57  // Metadata contains metadata of RecordSet response
    58  type Metadata struct {
    59  	LastPage      int  `json:"lastPage"`
    60  	Page          int  `json:"page"`
    61  	PageSize      int  `json:"pageSize"`
    62  	ShowAll       bool `json:"showAll"`
    63  	TotalElements int  `json:"totalElements"`
    64  }
    65  
    66  // RecordSetResponse contains a response with a list of record sets
    67  type RecordSetResponse struct {
    68  	Metadata   Metadata    `json:"metadata"`
    69  	RecordSets []RecordSet `json:"recordsets"`
    70  }
    71  
    72  // Validate validates RecordSets
    73  func (rs *RecordSets) Validate() error {
    74  	if len(rs.RecordSets) < 1 {
    75  		return fmt.Errorf("request initiated with empty recordsets list")
    76  	}
    77  	for _, rec := range rs.RecordSets {
    78  		err := validation.Errors{
    79  			"Name":  validation.Validate(rec.Name, validation.Required),
    80  			"Type":  validation.Validate(rec.Type, validation.Required),
    81  			"TTL":   validation.Validate(rec.TTL, validation.Required),
    82  			"Rdata": validation.Validate(rec.Rdata, validation.Required),
    83  		}.Filter()
    84  		if err != nil {
    85  			return err
    86  		}
    87  	}
    88  	return nil
    89  }
    90  
    91  func (d *dns) GetRecordSets(ctx context.Context, zone string, queryArgs ...RecordSetQueryArgs) (*RecordSetResponse, error) {
    92  	logger := d.Log(ctx)
    93  	logger.Debug("GetRecordSets")
    94  
    95  	if len(queryArgs) > 1 {
    96  		return nil, fmt.Errorf("invalid arguments GetRecordSets QueryArgs")
    97  	}
    98  
    99  	getURL := fmt.Sprintf("/config-dns/v2/zones/%s/recordsets", zone)
   100  
   101  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
   102  	if err != nil {
   103  		return nil, fmt.Errorf("failed to create GetRecordsets request: %w", err)
   104  	}
   105  
   106  	q := req.URL.Query()
   107  	if len(queryArgs) > 0 {
   108  		if queryArgs[0].Page > 0 {
   109  			q.Add("page", strconv.Itoa(queryArgs[0].Page))
   110  		}
   111  		if queryArgs[0].PageSize > 0 {
   112  			q.Add("pageSize", strconv.Itoa(queryArgs[0].PageSize))
   113  		}
   114  		if queryArgs[0].Search != "" {
   115  			q.Add("search", queryArgs[0].Search)
   116  		}
   117  		q.Add("showAll", strconv.FormatBool(queryArgs[0].ShowAll))
   118  		if queryArgs[0].SortBy != "" {
   119  			q.Add("sortBy", queryArgs[0].SortBy)
   120  		}
   121  		if queryArgs[0].Types != "" {
   122  			q.Add("types", queryArgs[0].Types)
   123  		}
   124  		req.URL.RawQuery = q.Encode()
   125  	}
   126  
   127  	var result RecordSetResponse
   128  	resp, err := d.Exec(req, &result)
   129  	if err != nil {
   130  		return nil, fmt.Errorf("GetRecordsets request failed: %w", err)
   131  	}
   132  
   133  	if resp.StatusCode != http.StatusOK {
   134  		return nil, d.Error(resp)
   135  	}
   136  
   137  	return &result, nil
   138  }
   139  
   140  func (d *dns) CreateRecordSets(ctx context.Context, recordSets *RecordSets, zone string, recLock ...bool) error {
   141  	// This lock will restrict the concurrency of API calls
   142  	// to 1 save request at a time. This is needed for the Soa.Serial value which
   143  	// is required to be incremented for every subsequent update to a zone
   144  	// so we have to save just one request at a time to ensure this is always
   145  	// incremented properly
   146  
   147  	if localLock(recLock) {
   148  		zoneRecordSetsWriteLock.Lock()
   149  		defer zoneRecordSetsWriteLock.Unlock()
   150  	}
   151  
   152  	logger := d.Log(ctx)
   153  	logger.Debug("CreateRecordSets")
   154  
   155  	if err := recordSets.Validate(); err != nil {
   156  		return err
   157  	}
   158  
   159  	reqBody, err := convertStructToReqBody(recordSets)
   160  	if err != nil {
   161  		return fmt.Errorf("failed to generate request body: %w", err)
   162  	}
   163  
   164  	postURL := fmt.Sprintf("/config-dns/v2/zones/%s/recordsets", zone)
   165  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqBody)
   166  	if err != nil {
   167  		return fmt.Errorf("failed to create CreateRecordsets request: %w", err)
   168  	}
   169  
   170  	resp, err := d.Exec(req, nil)
   171  	if err != nil {
   172  		return fmt.Errorf("CreateRecordsets request failed: %w", err)
   173  	}
   174  
   175  	if resp.StatusCode != http.StatusNoContent {
   176  		return d.Error(resp)
   177  	}
   178  
   179  	return nil
   180  }
   181  
   182  func (d *dns) UpdateRecordSets(ctx context.Context, recordSets *RecordSets, zone string, recLock ...bool) error {
   183  	// This lock will restrict the concurrency of API calls
   184  	// to 1 save request at a time. This is needed for the Soa.Serial value which
   185  	// is required to be incremented for every subsequent update to a zone
   186  	// so we have to save just one request at a time to ensure this is always
   187  	// incremented properly
   188  
   189  	if localLock(recLock) {
   190  		zoneRecordSetsWriteLock.Lock()
   191  		defer zoneRecordSetsWriteLock.Unlock()
   192  	}
   193  
   194  	logger := d.Log(ctx)
   195  	logger.Debug("UpdateRecordsets")
   196  
   197  	if err := recordSets.Validate(); err != nil {
   198  		return err
   199  	}
   200  
   201  	reqBody, err := convertStructToReqBody(recordSets)
   202  	if err != nil {
   203  		return fmt.Errorf("failed to generate request body: %w", err)
   204  	}
   205  
   206  	putURL := fmt.Sprintf("/config-dns/v2/zones/%s/recordsets", zone)
   207  	req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, reqBody)
   208  	if err != nil {
   209  		return fmt.Errorf("failed to create UpdateRecordsets request: %w", err)
   210  	}
   211  
   212  	resp, err := d.Exec(req, nil)
   213  	if err != nil {
   214  		return fmt.Errorf("UpdateRecordsets request failed: %w", err)
   215  	}
   216  
   217  	if resp.StatusCode != http.StatusNoContent {
   218  		return d.Error(resp)
   219  	}
   220  
   221  	return nil
   222  }