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