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

     1  package dns
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"net/http"
     9  
    10  	"bytes"
    11  	"encoding/json"
    12  	"reflect"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  )
    17  
    18  var (
    19  	zoneWriteLock sync.Mutex
    20  )
    21  
    22  type (
    23  	// Zones contains operations available on Zone resources
    24  	// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html
    25  	Zones interface {
    26  		// ListZones retrieves a list of all zones user can access
    27  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getzones
    28  		ListZones(context.Context, ...ZoneListQueryArgs) (*ZoneListResponse, error)
    29  		// NewZone returns a new ZoneCreate object
    30  		NewZone(context.Context, ZoneCreate) *ZoneCreate
    31  		// NewZoneResponse returns a new ZoneResponse object
    32  		NewZoneResponse(context.Context, string) *ZoneResponse
    33  		// NewChangeListResponse returns a new ChangeListResponse object
    34  		NewChangeListResponse(context.Context, string) *ChangeListResponse
    35  		// NewZoneQueryString returns a new ZoneQueryString object
    36  		NewZoneQueryString(context.Context, string, string) *ZoneQueryString
    37  		// GetZone retrieves Zone metadata
    38  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getzone
    39  		GetZone(context.Context, string) (*ZoneResponse, error)
    40  		//GetChangeList treieves Zone changelist
    41  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getchangelist
    42  		GetChangeList(context.Context, string) (*ChangeListResponse, error)
    43  		// GetMasterZoneFile retrieves master zone file
    44  		// https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getversionmasterzonefile
    45  		GetMasterZoneFile(context.Context, string) (string, error)
    46  		// PostMasterZoneFile updates master zone file
    47  		// https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#postmasterzonefile
    48  		PostMasterZoneFile(context.Context, string, string) error
    49  		// CreateZone
    50  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#postzones
    51  		CreateZone(context.Context, *ZoneCreate, ZoneQueryString, ...bool) error
    52  		// SaveChangelist
    53  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#postchangelists
    54  		SaveChangelist(context.Context, *ZoneCreate) error
    55  		// SubmitChangelist submits changelist for the Zone to create default NS SOA records
    56  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#postchangelistsubmit
    57  		SubmitChangelist(context.Context, *ZoneCreate) error
    58  		// UpdateZone updates the Zone
    59  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#putzone
    60  		UpdateZone(context.Context, *ZoneCreate, ZoneQueryString) error
    61  		// DeleteZone a zone
    62  		// See: N/A
    63  		DeleteZone(context.Context, *ZoneCreate, ZoneQueryString) error
    64  		// ValidateZone validates zone metadata based on type
    65  		ValidateZone(context.Context, *ZoneCreate) error
    66  		// GetZoneNames retrieves a list of a zone's record names
    67  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getzonerecordsetnames
    68  		GetZoneNames(context.Context, string) (*ZoneNamesResponse, error)
    69  		// GetZoneNameTypes retrieves a zone name's record types
    70  		// See: https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getzonerecordsettypes
    71  		GetZoneNameTypes(context.Context, string, string) (*ZoneNameTypesResponse, error)
    72  		// CreateBulkZones submits create bulk zone request
    73  		// https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#postbulkzonecreate
    74  		CreateBulkZones(context.Context, *BulkZonesCreate, ZoneQueryString) (*BulkZonesResponse, error)
    75  		// DeleteBulkZones submits delete bulk zone request
    76  		// https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#postbulkzonedelete
    77  		DeleteBulkZones(context.Context, *ZoneNameListResponse, ...bool) (*BulkZonesResponse, error)
    78  		// GetBulkZoneCreateStatus retrieves submit request status
    79  		// https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getbulkzonecreatestatus
    80  		GetBulkZoneCreateStatus(context.Context, string) (*BulkStatusResponse, error)
    81  		//GetBulkZoneDeleteStatus retrieves submit request status
    82  		// https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getbulkzonedeletestatus
    83  		GetBulkZoneDeleteStatus(context.Context, string) (*BulkStatusResponse, error)
    84  		// GetBulkZoneCreateResult retrieves create request result
    85  		// https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getbulkzonecreateresult
    86  		GetBulkZoneCreateResult(ctx context.Context, requestid string) (*BulkCreateResultResponse, error)
    87  		// GetBulkZoneDeleteResult retrieves delete request result
    88  		// https://developer.akamai.com/api/cloud_security/edge_dns_zone_management/v2.html#getbulkzonedeleteresult
    89  		GetBulkZoneDeleteResult(context.Context, string) (*BulkDeleteResultResponse, error)
    90  	}
    91  
    92  	// ZoneQueryString contains zone query parameters
    93  	ZoneQueryString struct {
    94  		Contract string
    95  		Group    string
    96  	}
    97  
    98  	// ZoneCreate contains zone create request
    99  	ZoneCreate struct {
   100  		Zone                  string   `json:"zone"`
   101  		Type                  string   `json:"type"`
   102  		Masters               []string `json:"masters,omitempty"`
   103  		Comment               string   `json:"comment,omitempty"`
   104  		SignAndServe          bool     `json:"signAndServe"`
   105  		SignAndServeAlgorithm string   `json:"signAndServeAlgorithm,omitempty"`
   106  		TsigKey               *TSIGKey `json:"tsigKey,omitempty"`
   107  		Target                string   `json:"target,omitempty"`
   108  		EndCustomerID         string   `json:"endCustomerId,omitempty"`
   109  		ContractID            string   `json:"contractId,omitempty"`
   110  	}
   111  
   112  	// ZoneResponse contains zone create response
   113  	ZoneResponse struct {
   114  		Zone                  string   `json:"zone,omitempty"`
   115  		Type                  string   `json:"type,omitempty"`
   116  		Masters               []string `json:"masters,omitempty"`
   117  		Comment               string   `json:"comment,omitempty"`
   118  		SignAndServe          bool     `json:"signAndServe"`
   119  		SignAndServeAlgorithm string   `json:"signAndServeAlgorithm,omitempty"`
   120  		TsigKey               *TSIGKey `json:"tsigKey,omitempty"`
   121  		Target                string   `json:"target,omitempty"`
   122  		EndCustomerID         string   `json:"endCustomerId,omitempty"`
   123  		ContractID            string   `json:"contractId,omitempty"`
   124  		AliasCount            int64    `json:"aliasCount,omitempty"`
   125  		ActivationState       string   `json:"activationState,omitempty"`
   126  		LastActivationDate    string   `json:"lastActivationDate,omitempty"`
   127  		LastModifiedBy        string   `json:"lastModifiedBy,omitempty"`
   128  		LastModifiedDate      string   `json:"lastModifiedDate,omitempty"`
   129  		VersionId             string   `json:"versionId,omitempty"`
   130  	}
   131  
   132  	// ZoneListQueryArgs contains parameters for List Zones query
   133  	ZoneListQueryArgs struct {
   134  		ContractIDs string
   135  		Page        int
   136  		PageSize    int
   137  		Search      string
   138  		ShowAll     bool
   139  		SortBy      string
   140  		Types       string
   141  	}
   142  
   143  	// ListMetadata contains metadata for List Zones request
   144  	ListMetadata struct {
   145  		ContractIDs   []string `json:"contractIds"`
   146  		Page          int      `json:"page"`
   147  		PageSize      int      `json:"pageSize"`
   148  		ShowAll       bool     `json:"showAll"`
   149  		TotalElements int      `json:"totalElements"`
   150  	} //`json:"metadata"`
   151  
   152  	// ZoneListResponse contains response for List Zones request
   153  	ZoneListResponse struct {
   154  		Metadata *ListMetadata   `json:"metadata,omitempty"`
   155  		Zones    []*ZoneResponse `json:"zones,omitempty"`
   156  	}
   157  
   158  	// ChangeListResponse contains metadata about a change list
   159  	ChangeListResponse struct {
   160  		Zone             string `json:"zone,omitempty"`
   161  		ChangeTag        string `json:"changeTag,omitempty"`
   162  		ZoneVersionID    string `json:"zoneVersionId,omitempty"`
   163  		LastModifiedDate string `json:"lastModifiedDate,omitempty"`
   164  		Stale            bool   `json:"stale,omitempty"`
   165  	}
   166  
   167  	// ZoneNameListResponse contains response with a list of zone's names and aliases
   168  	ZoneNameListResponse struct {
   169  		Zones   []string `json:"zones"`
   170  		Aliases []string `json:"aliases,omitempty"`
   171  	}
   172  
   173  	// ZoneNamesResponse contains record set names for zone
   174  	ZoneNamesResponse struct {
   175  		Names []string `json:"names"`
   176  	}
   177  
   178  	// ZoneNameTypesResponse contains record set types for zone
   179  	ZoneNameTypesResponse struct {
   180  		Types []string `json:"types"`
   181  	}
   182  )
   183  
   184  var zoneStructMap = map[string]string{
   185  	"Zone":                  "zone",
   186  	"Type":                  "type",
   187  	"Masters":               "masters",
   188  	"Comment":               "comment",
   189  	"SignAndServe":          "signAndServe",
   190  	"SignAndServeAlgorithm": "signAndServeAlgorithm",
   191  	"TsigKey":               "tsigKey",
   192  	"Target":                "target",
   193  	"EndCustomerID":         "endCustomerId",
   194  	"ContractId":            "contractId"}
   195  
   196  // Util to convert struct to http request body, eg. io.reader
   197  func convertStructToReqBody(srcstruct interface{}) (io.Reader, error) {
   198  
   199  	reqbody, err := json.Marshal(srcstruct)
   200  	if err != nil {
   201  		return nil, err
   202  	}
   203  	return bytes.NewBuffer(reqbody), nil
   204  }
   205  
   206  // List Zones
   207  func (p *dns) ListZones(ctx context.Context, queryArgs ...ZoneListQueryArgs) (*ZoneListResponse, error) {
   208  
   209  	logger := p.Log(ctx)
   210  	logger.Debug("ListZones")
   211  
   212  	// construct GET url
   213  	getURL := fmt.Sprintf("/config-dns/v2/zones")
   214  	if len(queryArgs) > 1 {
   215  		return nil, fmt.Errorf("ListZones QueryArgs invalid")
   216  	}
   217  
   218  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
   219  	if err != nil {
   220  		return nil, fmt.Errorf("failed to create listzones request: %w", err)
   221  	}
   222  
   223  	q := req.URL.Query()
   224  	if len(queryArgs) > 0 {
   225  		if queryArgs[0].Page > 0 {
   226  			q.Add("page", strconv.Itoa(queryArgs[0].Page))
   227  		}
   228  		if queryArgs[0].PageSize > 0 {
   229  			q.Add("pageSize", strconv.Itoa(queryArgs[0].PageSize))
   230  		}
   231  		if queryArgs[0].Search != "" {
   232  			q.Add("search", queryArgs[0].Search)
   233  		}
   234  		q.Add("showAll", strconv.FormatBool(queryArgs[0].ShowAll))
   235  		if queryArgs[0].SortBy != "" {
   236  			q.Add("sortBy", queryArgs[0].SortBy)
   237  		}
   238  		if queryArgs[0].Types != "" {
   239  			q.Add("types", queryArgs[0].Types)
   240  		}
   241  		if queryArgs[0].ContractIDs != "" {
   242  			q.Add("contractIds", queryArgs[0].ContractIDs)
   243  		}
   244  		req.URL.RawQuery = q.Encode()
   245  	}
   246  
   247  	var zonelist ZoneListResponse
   248  	resp, err := p.Exec(req, &zonelist)
   249  	if err != nil {
   250  		return nil, fmt.Errorf("listzones request failed: %w", err)
   251  	}
   252  
   253  	if resp.StatusCode != http.StatusOK {
   254  		return nil, p.Error(resp)
   255  	}
   256  
   257  	return &zonelist, nil
   258  }
   259  
   260  // NewZone creates a new Zone. Supports subset of fields
   261  func (p *dns) NewZone(ctx context.Context, params ZoneCreate) *ZoneCreate {
   262  
   263  	logger := p.Log(ctx)
   264  	logger.Debug("NewZone")
   265  
   266  	zone := &ZoneCreate{Zone: params.Zone,
   267  		Type:                  params.Type,
   268  		Masters:               params.Masters,
   269  		TsigKey:               params.TsigKey,
   270  		Target:                params.Target,
   271  		EndCustomerID:         params.EndCustomerID,
   272  		ContractID:            params.ContractID,
   273  		Comment:               params.Comment,
   274  		SignAndServe:          params.SignAndServe,
   275  		SignAndServeAlgorithm: params.SignAndServeAlgorithm}
   276  
   277  	logger.Debugf("Created zone: %v", zone)
   278  	return zone
   279  }
   280  
   281  func (p *dns) NewZoneResponse(ctx context.Context, zonename string) *ZoneResponse {
   282  
   283  	logger := p.Log(ctx)
   284  	logger.Debug("NewZoneResponse")
   285  
   286  	zone := &ZoneResponse{Zone: zonename}
   287  	return zone
   288  }
   289  
   290  func (p *dns) NewChangeListResponse(ctx context.Context, zone string) *ChangeListResponse {
   291  
   292  	logger := p.Log(ctx)
   293  	logger.Debug("NewChangeListResponse")
   294  
   295  	changelist := &ChangeListResponse{Zone: zone}
   296  	return changelist
   297  }
   298  
   299  func (p *dns) NewZoneQueryString(ctx context.Context, contract string, group string) *ZoneQueryString {
   300  
   301  	logger := p.Log(ctx)
   302  	logger.Debug("NewZoneQueryString")
   303  
   304  	zonequerystring := &ZoneQueryString{Contract: contract, Group: group}
   305  	return zonequerystring
   306  }
   307  
   308  // GetZone retrieves a DNS Zone for a given hostname
   309  func (p *dns) GetZone(ctx context.Context, zonename string) (*ZoneResponse, error) {
   310  
   311  	logger := p.Log(ctx)
   312  	logger.Debug("GetZone")
   313  
   314  	var zone ZoneResponse
   315  
   316  	getURL := fmt.Sprintf("/config-dns/v2/zones/%s", zonename)
   317  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
   318  	if err != nil {
   319  		return nil, fmt.Errorf("failed to create GetZone request: %w", err)
   320  	}
   321  
   322  	resp, err := p.Exec(req, &zone)
   323  	if err != nil {
   324  		return nil, fmt.Errorf("GetZone request failed: %w", err)
   325  	}
   326  
   327  	if resp.StatusCode != http.StatusOK {
   328  		return nil, p.Error(resp)
   329  	}
   330  
   331  	return &zone, nil
   332  }
   333  
   334  // GetChangeList retrieves a changelist for a zone
   335  func (p *dns) GetChangeList(ctx context.Context, zone string) (*ChangeListResponse, error) {
   336  
   337  	logger := p.Log(ctx)
   338  	logger.Debug("GetChangeList")
   339  
   340  	var changelist ChangeListResponse
   341  	getURL := fmt.Sprintf("/config-dns/v2/changelists/%s", zone)
   342  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
   343  	if err != nil {
   344  		return nil, fmt.Errorf("failed to create GetChangeList request: %w", err)
   345  	}
   346  
   347  	resp, err := p.Exec(req, &changelist)
   348  	if err != nil {
   349  		return nil, fmt.Errorf("GetChangeList request failed: %w", err)
   350  	}
   351  
   352  	if resp.StatusCode != http.StatusOK {
   353  		return nil, p.Error(resp)
   354  	}
   355  
   356  	return &changelist, nil
   357  }
   358  
   359  // GetMasterZoneFile retrieves the zone's master file
   360  func (p *dns) GetMasterZoneFile(ctx context.Context, zone string) (string, error) {
   361  
   362  	logger := p.Log(ctx)
   363  	logger.Debug("GetMasterZoneFile")
   364  
   365  	getURL := fmt.Sprintf("/config-dns/v2/zones/%s/zone-file", zone)
   366  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
   367  	if err != nil {
   368  		return "", fmt.Errorf("failed to create GetMasterZoneFile request: %w", err)
   369  	}
   370  	req.Header.Add("Accept", "text/dns")
   371  
   372  	resp, err := p.Exec(req, nil)
   373  	if err != nil {
   374  		return "", fmt.Errorf("GetMasterZoneFile request failed: %w", err)
   375  	}
   376  
   377  	if resp.StatusCode != http.StatusOK {
   378  		return "", p.Error(resp)
   379  	}
   380  
   381  	masterfile, err := ioutil.ReadAll(resp.Body)
   382  	if err != nil {
   383  		return "", fmt.Errorf("GetMasterZoneFile request failed: %w", err)
   384  	}
   385  
   386  	return string(masterfile), nil
   387  }
   388  
   389  // Update Master Zone file
   390  func (p *dns) PostMasterZoneFile(ctx context.Context, zone string, filedata string) error {
   391  
   392  	logger := p.Log(ctx)
   393  	logger.Debug("PostMasterZoneFile")
   394  
   395  	mtresp := ""
   396  	pmzfURL := fmt.Sprintf("/config-dns/v2/zones/%s/zone-file", zone)
   397  	buf := bytes.NewReader([]byte(filedata))
   398  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, pmzfURL, buf)
   399  	if err != nil {
   400  		return fmt.Errorf("failed to create PostMasterZoneFile request: %w", err)
   401  	}
   402  
   403  	req.Header.Set("Content-Type", "text/dns")
   404  
   405  	resp, err := p.Exec(req, &mtresp)
   406  	if err != nil {
   407  		return fmt.Errorf("Create PostMasterZoneFile failed: %w", err)
   408  	}
   409  
   410  	if resp.StatusCode != http.StatusNoContent {
   411  		return p.Error(resp)
   412  	}
   413  
   414  	return nil
   415  }
   416  
   417  // Create a Zone
   418  func (p *dns) CreateZone(ctx context.Context, zone *ZoneCreate, zonequerystring ZoneQueryString, clearConn ...bool) error {
   419  	// This lock will restrict the concurrency of API calls
   420  	// to 1 save request at a time. This is needed for the Soa.Serial value which
   421  	// is required to be incremented for every subsequent update to a zone
   422  	// so we have to save just one request at a time to ensure this is always
   423  	// incremented properly
   424  
   425  	zoneWriteLock.Lock()
   426  	defer zoneWriteLock.Unlock()
   427  
   428  	logger := p.Log(ctx)
   429  	logger.Debug("Zone Create")
   430  
   431  	if err := p.ValidateZone(ctx, zone); err != nil {
   432  		return err
   433  	}
   434  
   435  	zoneMap := filterZoneCreate(zone)
   436  
   437  	var zoneresponse ZoneResponse
   438  	zoneURL := "/config-dns/v2/zones/?contractId=" + zonequerystring.Contract
   439  	if len(zonequerystring.Group) > 0 {
   440  		zoneURL += "&gid=" + zonequerystring.Group
   441  	}
   442  
   443  	reqbody, err := convertStructToReqBody(zoneMap)
   444  	if err != nil {
   445  		return fmt.Errorf("failed to generate request body: %w", err)
   446  	}
   447  
   448  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, zoneURL, reqbody)
   449  	if err != nil {
   450  		return fmt.Errorf("failed to create Zone Create request: %w", err)
   451  	}
   452  
   453  	resp, err := p.Exec(req, &zoneresponse)
   454  	if err != nil {
   455  		return fmt.Errorf("Create Zone request failed: %w", err)
   456  	}
   457  
   458  	if resp.StatusCode != http.StatusCreated {
   459  		return p.Error(resp)
   460  	}
   461  
   462  	if strings.ToUpper(zone.Type) == "PRIMARY" {
   463  		// Timing issue with Create immediately followed by SaveChangelist
   464  		for _, clear := range clearConn {
   465  			// should only be one entry
   466  			if clear {
   467  				logger.Info("Clearing Idle Connections")
   468  				p.Client().CloseIdleConnections()
   469  			}
   470  		}
   471  	}
   472  
   473  	return nil
   474  }
   475  
   476  // Create changelist for the Zone. Side effect is to create default NS SOA records
   477  func (p *dns) SaveChangelist(ctx context.Context, zone *ZoneCreate) error {
   478  	// This lock will restrict the concurrency of API calls
   479  	// to 1 save request at a time. This is needed for the Soa.Serial value which
   480  	// is required to be incremented for every subsequent update to a zone
   481  	// so we have to save just one request at a time to ensure this is always
   482  	// incremented properly
   483  
   484  	zoneWriteLock.Lock()
   485  	defer zoneWriteLock.Unlock()
   486  
   487  	logger := p.Log(ctx)
   488  	logger.Debug("SaveChangeList")
   489  
   490  	reqbody, err := convertStructToReqBody("")
   491  	if err != nil {
   492  		return fmt.Errorf("failed to generate request body: %w", err)
   493  	}
   494  
   495  	postURL := fmt.Sprintf("/config-dns/v2/changelists/?zone=%s", zone.Zone)
   496  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqbody)
   497  	if err != nil {
   498  		return fmt.Errorf("failed to create SaveChangeList request: %w", err)
   499  	}
   500  
   501  	resp, err := p.Exec(req, nil)
   502  	if err != nil {
   503  		return fmt.Errorf("SaveChangeList request failed: %w", err)
   504  	}
   505  
   506  	if resp.StatusCode != http.StatusCreated {
   507  		return p.Error(resp)
   508  	}
   509  
   510  	return nil
   511  }
   512  
   513  // Save changelist for the Zone to create default NS SOA records
   514  func (p *dns) SubmitChangelist(ctx context.Context, zone *ZoneCreate) error {
   515  	// This lock will restrict the concurrency of API calls
   516  	// to 1 save request at a time. This is needed for the Soa.Serial value which
   517  	// is required to be incremented for every subsequent update to a zone
   518  	// so we have to save just one request at a time to ensure this is always
   519  	// incremented properly
   520  
   521  	zoneWriteLock.Lock()
   522  	defer zoneWriteLock.Unlock()
   523  
   524  	logger := p.Log(ctx)
   525  	logger.Debug("SubmitChangeList")
   526  
   527  	reqbody, err := convertStructToReqBody("")
   528  	if err != nil {
   529  		return fmt.Errorf("failed to generate request body: %w", err)
   530  	}
   531  
   532  	postURL := fmt.Sprintf("/config-dns/v2/changelists/%s/submit", zone.Zone)
   533  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, reqbody)
   534  	if err != nil {
   535  		return fmt.Errorf("failed to create SubmitChangeList request: %w", err)
   536  	}
   537  
   538  	resp, err := p.Exec(req, nil)
   539  	if err != nil {
   540  		return fmt.Errorf("SubmitChangeList request failed: %w", err)
   541  	}
   542  
   543  	if resp.StatusCode != http.StatusNoContent {
   544  		return p.Error(resp)
   545  	}
   546  
   547  	return nil
   548  }
   549  
   550  // Save updates the Zone
   551  func (p *dns) UpdateZone(ctx context.Context, zone *ZoneCreate, _ ZoneQueryString) error {
   552  	// This lock will restrict the concurrency of API calls
   553  	// to 1 save request at a time. This is needed for the Soa.Serial value which
   554  	// is required to be incremented for every subsequent update to a zone
   555  	// so we have to save just one request at a time to ensure this is always
   556  	// incremented properly
   557  
   558  	zoneWriteLock.Lock()
   559  	defer zoneWriteLock.Unlock()
   560  
   561  	logger := p.Log(ctx)
   562  	logger.Debug("Zone Update")
   563  
   564  	if err := p.ValidateZone(ctx, zone); err != nil {
   565  		return err
   566  	}
   567  
   568  	zoneMap := filterZoneCreate(zone)
   569  	reqbody, err := convertStructToReqBody(zoneMap)
   570  	if err != nil {
   571  		return fmt.Errorf("failed to generate request body: %w", err)
   572  	}
   573  
   574  	putURL := fmt.Sprintf("/config-dns/v2/zones/%s", zone.Zone)
   575  	req, err := http.NewRequestWithContext(ctx, http.MethodPut, putURL, reqbody)
   576  	if err != nil {
   577  		return fmt.Errorf("failed to create Get Update request: %w", err)
   578  	}
   579  
   580  	var zoneresp ZoneResponse
   581  	resp, err := p.Exec(req, &zoneresp)
   582  	if err != nil {
   583  		return fmt.Errorf("Zone Update request failed: %w", err)
   584  	}
   585  
   586  	if resp.StatusCode != http.StatusOK {
   587  		return p.Error(resp)
   588  	}
   589  
   590  	return nil
   591  
   592  }
   593  
   594  // Zone Delete.
   595  func (p *dns) DeleteZone(ctx context.Context, zone *ZoneCreate, _ ZoneQueryString) error {
   596  	// remove all the records except for SOA
   597  	// which is required and save the zone
   598  
   599  	zoneWriteLock.Lock()
   600  	defer zoneWriteLock.Unlock()
   601  
   602  	logger := p.Log(ctx)
   603  	logger.Debug("Zone Delete")
   604  
   605  	if zone.Zone == "" {
   606  		return fmt.Errorf("Zone name missing")
   607  	}
   608  
   609  	deleteURL := fmt.Sprintf("/config-dns/v2/zones/%s", zone.Zone)
   610  	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, deleteURL, nil)
   611  	if err != nil {
   612  		return fmt.Errorf("failed to create Zone Delete request: %w", err)
   613  	}
   614  
   615  	resp, err := p.Exec(req, nil)
   616  	if err != nil {
   617  		return fmt.Errorf("Zone Delete request failed: %w", err)
   618  	}
   619  
   620  	if resp.StatusCode == http.StatusNotFound {
   621  		return nil
   622  	}
   623  
   624  	if resp.StatusCode != http.StatusNoContent {
   625  		return p.Error(resp)
   626  	}
   627  
   628  	return nil
   629  
   630  }
   631  
   632  func filterZoneCreate(zone *ZoneCreate) map[string]interface{} {
   633  
   634  	zoneType := strings.ToUpper(zone.Type)
   635  	filteredZone := make(map[string]interface{})
   636  	zoneElems := reflect.ValueOf(zone).Elem()
   637  	for i := 0; i < zoneElems.NumField(); i++ {
   638  		varName := zoneElems.Type().Field(i).Name
   639  		varLower := zoneStructMap[varName]
   640  		varValue := zoneElems.Field(i).Interface()
   641  		switch varName {
   642  		case "Target":
   643  			if zoneType == "ALIAS" {
   644  				filteredZone[varLower] = varValue
   645  			}
   646  		case "TsigKey":
   647  			if zoneType == "SECONDARY" {
   648  				filteredZone[varLower] = varValue
   649  			}
   650  		case "Masters":
   651  			if zoneType == "SECONDARY" {
   652  				filteredZone[varLower] = varValue
   653  			}
   654  		case "SignAndServe":
   655  			if zoneType != "ALIAS" {
   656  				filteredZone[varLower] = varValue
   657  			}
   658  		case "SignAndServeAlgorithm":
   659  			if zoneType != "ALIAS" {
   660  				filteredZone[varLower] = varValue
   661  			}
   662  		default:
   663  			filteredZone[varLower] = varValue
   664  		}
   665  	}
   666  
   667  	return filteredZone
   668  }
   669  
   670  // Validate ZoneCreate Object
   671  func (p *dns) ValidateZone(ctx context.Context, zone *ZoneCreate) error {
   672  
   673  	logger := p.Log(ctx)
   674  	logger.Debug("ValidateZone")
   675  
   676  	if len(zone.Zone) == 0 {
   677  		return fmt.Errorf("Zone name is required")
   678  	}
   679  	ztype := strings.ToUpper(zone.Type)
   680  	if ztype != "PRIMARY" && ztype != "SECONDARY" && ztype != "ALIAS" {
   681  		return fmt.Errorf("Invalid zone type")
   682  	}
   683  	if ztype != "SECONDARY" && zone.TsigKey != nil {
   684  		return fmt.Errorf("TsigKey is invalid for %s zone type", ztype)
   685  	}
   686  	if ztype == "ALIAS" {
   687  		if len(zone.Target) == 0 {
   688  			return fmt.Errorf("Target is required for Alias zone type")
   689  		}
   690  		if zone.Masters != nil && len(zone.Masters) > 0 {
   691  			return fmt.Errorf("Masters is invalid for Alias zone type")
   692  		}
   693  		if zone.SignAndServe {
   694  			return fmt.Errorf("SignAndServe is invalid for Alias zone type")
   695  		}
   696  		if len(zone.SignAndServeAlgorithm) > 0 {
   697  			return fmt.Errorf("SignAndServeAlgorithm is invalid for Alias zone type")
   698  		}
   699  		return nil
   700  	}
   701  	// Primary or Secondary
   702  	if len(zone.Target) > 0 {
   703  		return fmt.Errorf("Target is invalid for %s zone type", ztype)
   704  	}
   705  	if zone.Masters != nil && len(zone.Masters) > 0 && ztype == "PRIMARY" {
   706  		return fmt.Errorf("Masters is invalid for Primary zone type")
   707  	}
   708  
   709  	return nil
   710  }
   711  
   712  // Get Zone's Names
   713  func (p *dns) GetZoneNames(ctx context.Context, zone string) (*ZoneNamesResponse, error) {
   714  
   715  	logger := p.Log(ctx)
   716  	logger.Debug("GetZoneNames")
   717  
   718  	var znresponse ZoneNamesResponse
   719  	getURL := fmt.Sprintf("/config-dns/v2/zones/%s/names", zone)
   720  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
   721  	if err != nil {
   722  		return nil, fmt.Errorf("failed to create GetZoneNames request: %w", err)
   723  	}
   724  
   725  	resp, err := p.Exec(req, &znresponse)
   726  	if err != nil {
   727  		return nil, fmt.Errorf("GetZoneNames request failed: %w", err)
   728  	}
   729  
   730  	if resp.StatusCode != http.StatusOK {
   731  		return nil, p.Error(resp)
   732  	}
   733  
   734  	return &znresponse, nil
   735  }
   736  
   737  // Get Zone Name's record types
   738  func (p *dns) GetZoneNameTypes(ctx context.Context, zname string, zone string) (*ZoneNameTypesResponse, error) {
   739  
   740  	logger := p.Log(ctx)
   741  	logger.Debug(" GetZoneNameTypes")
   742  
   743  	var zntypes ZoneNameTypesResponse
   744  	getURL := fmt.Sprintf("/config-dns/v2/zones/%s/names/%s/types", zone, zname)
   745  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, getURL, nil)
   746  	if err != nil {
   747  		return nil, fmt.Errorf("failed to create GetZoneNameTypes request: %w", err)
   748  	}
   749  
   750  	resp, err := p.Exec(req, &zntypes)
   751  	if err != nil {
   752  		return nil, fmt.Errorf("GetZoneNameTypes request failed: %w", err)
   753  	}
   754  
   755  	if resp.StatusCode != http.StatusOK {
   756  		return nil, p.Error(resp)
   757  	}
   758  
   759  	return &zntypes, nil
   760  }