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

     1  package cloudwrapper
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"strconv"
    10  
    11  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr"
    12  	validation "github.com/go-ozzo/ozzo-validation/v4"
    13  )
    14  
    15  type (
    16  	// Configurations is a CloudWrapper configurations API interface
    17  	Configurations interface {
    18  		// GetConfiguration gets a specific Cloud Wrapper configuration
    19  		//
    20  		// See: https://techdocs.akamai.com/cloud-wrapper/reference/get-configuration
    21  		GetConfiguration(context.Context, GetConfigurationRequest) (*Configuration, error)
    22  		// ListConfigurations lists all Cloud Wrapper configurations on your contract
    23  		//
    24  		// See: https://techdocs.akamai.com/cloud-wrapper/reference/get-configurations
    25  		ListConfigurations(context.Context) (*ListConfigurationsResponse, error)
    26  		// CreateConfiguration creates a Cloud Wrapper configuration
    27  		//
    28  		// See: https://techdocs.akamai.com/cloud-wrapper/reference/post-configuration
    29  		CreateConfiguration(context.Context, CreateConfigurationRequest) (*Configuration, error)
    30  		// UpdateConfiguration updates a saved or inactive configuration
    31  		//
    32  		// See: https://techdocs.akamai.com/cloud-wrapper/reference/put-configuration
    33  		UpdateConfiguration(context.Context, UpdateConfigurationRequest) (*Configuration, error)
    34  		// DeleteConfiguration deletes configuration
    35  		//
    36  		// See: https://techdocs.akamai.com/cloud-wrapper/reference/delete-configuration
    37  		DeleteConfiguration(context.Context, DeleteConfigurationRequest) error
    38  		// ActivateConfiguration activates a Cloud Wrapper configuration
    39  		//
    40  		// See: https://techdocs.akamai.com/cloud-wrapper/reference/post-configuration-activations
    41  		ActivateConfiguration(context.Context, ActivateConfigurationRequest) error
    42  	}
    43  
    44  	// GetConfigurationRequest holds parameters for GetConfiguration
    45  	GetConfigurationRequest struct {
    46  		ConfigID int64
    47  	}
    48  
    49  	// CreateConfigurationRequest holds parameters for CreateConfiguration
    50  	CreateConfigurationRequest struct {
    51  		Activate bool
    52  		Body     CreateConfigurationBody
    53  	}
    54  
    55  	// CreateConfigurationBody holds request body parameters for CreateConfiguration
    56  	CreateConfigurationBody struct {
    57  		CapacityAlertsThreshold *int                `json:"capacityAlertsThreshold,omitempty"`
    58  		Comments                string              `json:"comments"`
    59  		ContractID              string              `json:"contractId"`
    60  		Locations               []ConfigLocationReq `json:"locations"`
    61  		MultiCDNSettings        *MultiCDNSettings   `json:"multiCdnSettings,omitempty"`
    62  		ConfigName              string              `json:"configName"`
    63  		NotificationEmails      []string            `json:"notificationEmails,omitempty"`
    64  		PropertyIDs             []string            `json:"propertyIds"`
    65  		RetainIdleObjects       bool                `json:"retainIdleObjects,omitempty"`
    66  	}
    67  
    68  	// UpdateConfigurationRequest holds parameters for UpdateConfiguration
    69  	UpdateConfigurationRequest struct {
    70  		ConfigID int64
    71  		Activate bool
    72  		Body     UpdateConfigurationBody
    73  	}
    74  
    75  	// UpdateConfigurationBody holds request body parameters for UpdateConfiguration
    76  	UpdateConfigurationBody struct {
    77  		CapacityAlertsThreshold *int                `json:"capacityAlertsThreshold,omitempty"`
    78  		Comments                string              `json:"comments"`
    79  		Locations               []ConfigLocationReq `json:"locations"`
    80  		MultiCDNSettings        *MultiCDNSettings   `json:"multiCdnSettings,omitempty"`
    81  		NotificationEmails      []string            `json:"notificationEmails,omitempty"`
    82  		PropertyIDs             []string            `json:"propertyIds"`
    83  		RetainIdleObjects       bool                `json:"retainIdleObjects,omitempty"`
    84  	}
    85  
    86  	// DeleteConfigurationRequest holds parameters for DeleteConfiguration
    87  	DeleteConfigurationRequest struct {
    88  		ConfigID int64
    89  	}
    90  
    91  	// ActivateConfigurationRequest holds parameters for ActivateConfiguration
    92  	ActivateConfigurationRequest struct {
    93  		ConfigurationIDs []int `json:"configurationIds"`
    94  	}
    95  
    96  	// Configuration represents CloudWrapper configuration
    97  	Configuration struct {
    98  		CapacityAlertsThreshold *int                 `json:"capacityAlertsThreshold"`
    99  		Comments                string               `json:"comments"`
   100  		ContractID              string               `json:"contractId"`
   101  		ConfigID                int64                `json:"configId"`
   102  		Locations               []ConfigLocationResp `json:"locations"`
   103  		MultiCDNSettings        *MultiCDNSettings    `json:"multiCdnSettings"`
   104  		Status                  StatusType           `json:"status"`
   105  		ConfigName              string               `json:"configName"`
   106  		LastUpdatedBy           string               `json:"lastUpdatedBy"`
   107  		LastUpdatedDate         string               `json:"lastUpdatedDate"`
   108  		LastActivatedBy         *string              `json:"lastActivatedBy"`
   109  		LastActivatedDate       *string              `json:"lastActivatedDate"`
   110  		NotificationEmails      []string             `json:"notificationEmails"`
   111  		PropertyIDs             []string             `json:"propertyIds"`
   112  		RetainIdleObjects       bool                 `json:"retainIdleObjects"`
   113  	}
   114  
   115  	// ListConfigurationsResponse contains response from ListConfigurations
   116  	ListConfigurationsResponse struct {
   117  		Configurations []Configuration `json:"configurations"`
   118  	}
   119  
   120  	// ConfigLocationReq represents location to be configured for the configuration
   121  	ConfigLocationReq struct {
   122  		Comments      string   `json:"comments"`
   123  		TrafficTypeID int      `json:"trafficTypeId"`
   124  		Capacity      Capacity `json:"capacity"`
   125  	}
   126  
   127  	// ConfigLocationResp represents location to be configured for the configuration
   128  	ConfigLocationResp struct {
   129  		Comments      string   `json:"comments"`
   130  		TrafficTypeID int      `json:"trafficTypeId"`
   131  		Capacity      Capacity `json:"capacity"`
   132  		MapName       string   `json:"mapName"`
   133  	}
   134  
   135  	// MultiCDNSettings represents details about Multi CDN Settings
   136  	MultiCDNSettings struct {
   137  		BOCC             *BOCC        `json:"bocc"`
   138  		CDNs             []CDN        `json:"cdns"`
   139  		DataStreams      *DataStreams `json:"dataStreams"`
   140  		EnableSoftAlerts bool         `json:"enableSoftAlerts,omitempty"`
   141  		Origins          []Origin     `json:"origins"`
   142  	}
   143  
   144  	// BOCC represents diagnostic data beacon details
   145  	BOCC struct {
   146  		ConditionalSamplingFrequency SamplingFrequency `json:"conditionalSamplingFrequency,omitempty"`
   147  		Enabled                      bool              `json:"enabled"`
   148  		ForwardType                  ForwardType       `json:"forwardType,omitempty"`
   149  		RequestType                  RequestType       `json:"requestType,omitempty"`
   150  		SamplingFrequency            SamplingFrequency `json:"samplingFrequency,omitempty"`
   151  	}
   152  
   153  	// CDN represents a CDN added for the configuration
   154  	CDN struct {
   155  		CDNAuthKeys []CDNAuthKey `json:"cdnAuthKeys,omitempty"`
   156  		CDNCode     string       `json:"cdnCode"`
   157  		Enabled     bool         `json:"enabled"`
   158  		HTTPSOnly   bool         `json:"httpsOnly,omitempty"`
   159  		IPACLCIDRs  []string     `json:"ipAclCidrs,omitempty"`
   160  	}
   161  
   162  	// CDNAuthKey represents auth key configured for the CDN
   163  	CDNAuthKey struct {
   164  		AuthKeyName string `json:"authKeyName"`
   165  		ExpiryDate  string `json:"expiryDate,omitempty"`
   166  		HeaderName  string `json:"headerName,omitempty"`
   167  		Secret      string `json:"secret,omitempty"`
   168  	}
   169  
   170  	// DataStreams represents data streams details
   171  	DataStreams struct {
   172  		DataStreamIDs []int64 `json:"dataStreamIds,omitempty"`
   173  		Enabled       bool    `json:"enabled"`
   174  		SamplingRate  *int    `json:"samplingRate,omitempty"`
   175  	}
   176  
   177  	// Origin represents origin corresponding to the properties selected in the configuration
   178  	Origin struct {
   179  		Hostname   string `json:"hostname"`
   180  		OriginID   string `json:"originId"`
   181  		PropertyID int    `json:"propertyId"`
   182  	}
   183  
   184  	// SamplingFrequency is a type of sampling frequency. Either 'ZERO' or 'ONE_TENTH'
   185  	SamplingFrequency string
   186  
   187  	// ForwardType is a type of forward
   188  	ForwardType string
   189  
   190  	// RequestType is a type of request
   191  	RequestType string
   192  
   193  	// StatusType is a type of status
   194  	StatusType string
   195  )
   196  
   197  const (
   198  	// SamplingFrequencyZero represents SamplingFrequency value of 'ZERO'
   199  	SamplingFrequencyZero SamplingFrequency = "ZERO"
   200  	// SamplingFrequencyOneTenth represents SamplingFrequency value of 'ONE_TENTH'
   201  	SamplingFrequencyOneTenth SamplingFrequency = "ONE_TENTH"
   202  	// ForwardTypeOriginOnly represents ForwardType value of 'ORIGIN_ONLY'
   203  	ForwardTypeOriginOnly ForwardType = "ORIGIN_ONLY"
   204  	// ForwardTypeMidgressOnly represents ForwardType value of 'MIDGRESS_ONLY'
   205  	ForwardTypeMidgressOnly ForwardType = "MIDGRESS_ONLY"
   206  	// ForwardTypeOriginAndMidgress represents ForwardType value of 'ORIGIN_AND_MIDGRESS'
   207  	ForwardTypeOriginAndMidgress ForwardType = "ORIGIN_AND_MIDGRESS"
   208  	// RequestTypeEdgeOnly represents RequestType value of 'EDGE_ONLY'
   209  	RequestTypeEdgeOnly RequestType = "EDGE_ONLY"
   210  	// RequestTypeEdgeAndMidgress represents RequestType value of 'EDGE_AND_MIDGRESS'
   211  	RequestTypeEdgeAndMidgress RequestType = "EDGE_AND_MIDGRESS"
   212  
   213  	// StatusActive represents Status value of 'ACTIVE'
   214  	StatusActive StatusType = "ACTIVE"
   215  	// StatusSaved represents Status value of 'SAVED'
   216  	StatusSaved StatusType = "SAVED"
   217  	// StatusInProgress represents Status value of 'IN_PROGRESS'
   218  	StatusInProgress StatusType = "IN_PROGRESS"
   219  	// StatusDeleteInProgress represents Status value of 'DELETE_IN_PROGRESS'
   220  	StatusDeleteInProgress StatusType = "DELETE_IN_PROGRESS"
   221  	// StatusFailed represents Status value of 'FAILED'
   222  	StatusFailed StatusType = "FAILED"
   223  )
   224  
   225  // Validate validates GetConfigurationRequest
   226  func (r GetConfigurationRequest) Validate() error {
   227  	return edgegriderr.ParseValidationErrors(validation.Errors{
   228  		"ConfigID": validation.Validate(r.ConfigID, validation.Required),
   229  	})
   230  }
   231  
   232  // Validate validates CreateConfigurationRequest
   233  func (r CreateConfigurationRequest) Validate() error {
   234  	return edgegriderr.ParseValidationErrors(validation.Errors{
   235  		"Body": validation.Validate(r.Body, validation.Required),
   236  	})
   237  }
   238  
   239  // Validate validates CreateConfigurationBody
   240  func (b CreateConfigurationBody) Validate() error {
   241  	return validation.Errors{
   242  		"Comments":                validation.Validate(b.Comments, validation.Required),
   243  		"Locations":               validation.Validate(b.Locations, validation.Required),
   244  		"ConfigName":              validation.Validate(b.ConfigName, validation.Required),
   245  		"ContractID":              validation.Validate(b.ContractID, validation.Required),
   246  		"PropertyIDs":             validation.Validate(b.PropertyIDs, validation.Required),
   247  		"MultiCDNSettings":        validation.Validate(b.MultiCDNSettings),
   248  		"CapacityAlertsThreshold": validation.Validate(b.CapacityAlertsThreshold, validation.Min(50), validation.Max(100).Error(fmt.Sprintf("value '%d' is invalid. Must be between 50 and 100", b.CapacityAlertsThreshold))),
   249  	}.Filter()
   250  }
   251  
   252  // Validate validates UpdateConfigurationRequest
   253  func (r UpdateConfigurationRequest) Validate() error {
   254  	return edgegriderr.ParseValidationErrors(validation.Errors{
   255  		"ConfigID": validation.Validate(r.ConfigID, validation.Required),
   256  		"Body":     validation.Validate(r.Body, validation.Required),
   257  	})
   258  }
   259  
   260  // Validate validates UpdateConfigurationBody
   261  func (b UpdateConfigurationBody) Validate() error {
   262  	return validation.Errors{
   263  		"Comments":                validation.Validate(b.Comments, validation.Required),
   264  		"Locations":               validation.Validate(b.Locations, validation.Required),
   265  		"PropertyIDs":             validation.Validate(b.PropertyIDs, validation.Required),
   266  		"MultiCDNSettings":        validation.Validate(b.MultiCDNSettings),
   267  		"CapacityAlertsThreshold": validation.Validate(b.CapacityAlertsThreshold, validation.Min(50), validation.Max(100).Error(fmt.Sprintf("value '%d' is invalid. Must be between 50 and 100", b.CapacityAlertsThreshold))),
   268  	}.Filter()
   269  }
   270  
   271  // Validate validates DeleteConfigurationRequest
   272  func (r DeleteConfigurationRequest) Validate() error {
   273  	return edgegriderr.ParseValidationErrors(validation.Errors{
   274  		"ConfigID": validation.Validate(r.ConfigID, validation.Required),
   275  	})
   276  }
   277  
   278  // Validate validates ActivateConfigurationRequest
   279  func (r ActivateConfigurationRequest) Validate() error {
   280  	return edgegriderr.ParseValidationErrors(validation.Errors{
   281  		"ConfigurationIDs": validation.Validate(r.ConfigurationIDs, validation.Required),
   282  	})
   283  }
   284  
   285  // Validate validates ConfigurationLocation
   286  func (c ConfigLocationReq) Validate() error {
   287  	return validation.Errors{
   288  		"Comments":      validation.Validate(c.Comments, validation.Required),
   289  		"Capacity":      validation.Validate(c.Capacity, validation.Required),
   290  		"TrafficTypeID": validation.Validate(c.TrafficTypeID, validation.Required),
   291  	}.Filter()
   292  }
   293  
   294  // Validate validates Capacity
   295  func (c Capacity) Validate() error {
   296  	return validation.Errors{
   297  		"Unit":  validation.Validate(c.Unit, validation.Required, validation.In(UnitGB, UnitTB).Error(fmt.Sprintf("value '%s' is invalid. Must be one of: '%s', '%s'", c.Unit, UnitGB, UnitTB))),
   298  		"Value": validation.Validate(c.Value, validation.Required, validation.Min(1), validation.Max(int64(10000000000))),
   299  	}.Filter()
   300  }
   301  
   302  // Validate validates MultiCDNSettings
   303  func (m MultiCDNSettings) Validate() error {
   304  	return validation.Errors{
   305  		"BOCC":        validation.Validate(m.BOCC, validation.Required),
   306  		"CDNs":        validation.Validate(m.CDNs, validation.By(validateCDNs)),
   307  		"DataStreams": validation.Validate(m.DataStreams, validation.Required),
   308  		"Origins":     validation.Validate(m.Origins, validation.Required),
   309  	}.Filter()
   310  }
   311  
   312  // Validate validates BOCC
   313  func (b BOCC) Validate() error {
   314  	return validation.Errors{
   315  		"Enabled":                      validation.Validate(b.Enabled, validation.NotNil),
   316  		"ConditionalSamplingFrequency": validation.Validate(b.ConditionalSamplingFrequency, validation.Required.When(b.Enabled), validation.In(SamplingFrequencyZero, SamplingFrequencyOneTenth).Error(fmt.Sprintf("value '%s' is invalid. Must be one of: '%s', '%s'", b.ConditionalSamplingFrequency, SamplingFrequencyZero, SamplingFrequencyOneTenth))),
   317  		"ForwardType":                  validation.Validate(b.ForwardType, validation.Required.When(b.Enabled), validation.In(ForwardTypeOriginOnly, ForwardTypeMidgressOnly, ForwardTypeOriginAndMidgress).Error(fmt.Sprintf("value '%s' is invalid. Must be one of: '%s', '%s', '%s'", b.ForwardType, ForwardTypeOriginOnly, ForwardTypeMidgressOnly, ForwardTypeOriginAndMidgress))),
   318  		"RequestType":                  validation.Validate(b.RequestType, validation.Required.When(b.Enabled), validation.In(RequestTypeEdgeOnly, RequestTypeEdgeAndMidgress).Error(fmt.Sprintf("value '%s' is invalid. Must be one of: '%s', '%s'", b.RequestType, RequestTypeEdgeOnly, RequestTypeEdgeAndMidgress))),
   319  		"SamplingFrequency":            validation.Validate(b.SamplingFrequency, validation.Required.When(b.Enabled), validation.In(SamplingFrequencyZero, SamplingFrequencyOneTenth).Error(fmt.Sprintf("value '%s' is invalid. Must be one of: '%s', '%s'", b.RequestType, SamplingFrequencyZero, SamplingFrequencyOneTenth))),
   320  	}.Filter()
   321  }
   322  
   323  // Validate validates CDN
   324  func (c CDN) Validate() error {
   325  	return validation.Errors{
   326  		"CDNAuthKeys": validation.Validate(c.CDNAuthKeys),
   327  		"Enabled":     validation.Validate(c.Enabled, validation.NotNil),
   328  		"CDNCode":     validation.Validate(c.CDNCode, validation.Required),
   329  	}.Filter()
   330  }
   331  
   332  // Validate validates CDNAuthKey
   333  func (c CDNAuthKey) Validate() error {
   334  	return validation.Errors{
   335  		"AuthKeyName": validation.Validate(c.AuthKeyName, validation.Required),
   336  		"Secret":      validation.Validate(c.Secret, validation.Length(24, 24)),
   337  	}.Filter()
   338  }
   339  
   340  // Validate validates DataStreams
   341  func (d DataStreams) Validate() error {
   342  	return validation.Errors{
   343  		"Enabled":      validation.Validate(d.Enabled, validation.NotNil),
   344  		"SamplingRate": validation.Validate(d.SamplingRate, validation.When(d.SamplingRate != nil, validation.Min(1), validation.Max(100).Error(fmt.Sprintf("value '%d' is invalid. Must be between 1 and 100", d.SamplingRate)))),
   345  	}.Filter()
   346  }
   347  
   348  // Validate validates Origin
   349  func (o Origin) Validate() error {
   350  	return validation.Errors{
   351  		"PropertyID": validation.Validate(o.PropertyID, validation.Required),
   352  		"Hostname":   validation.Validate(o.Hostname, validation.Required),
   353  		"OriginID":   validation.Validate(o.OriginID, validation.Required),
   354  	}.Filter()
   355  }
   356  
   357  // validateCDNs validates CDNs by checking if at least one is enabled and one of authKeys or IP ACLs is specified
   358  func validateCDNs(value interface{}) error {
   359  	v, ok := value.([]CDN)
   360  	if !ok {
   361  		return fmt.Errorf("type %T is invalid. Must be []CDN", value)
   362  	}
   363  	if v == nil {
   364  		return fmt.Errorf("cannot be blank")
   365  	}
   366  	var isEnabled bool
   367  	for _, cdn := range v {
   368  		if cdn.Enabled {
   369  			isEnabled = true
   370  		}
   371  		if cdn.CDNAuthKeys == nil && cdn.IPACLCIDRs == nil {
   372  			return fmt.Errorf("at least one authentication method is required for CDN. Either IP ACL or header authentication must be enabled")
   373  		}
   374  	}
   375  	if !isEnabled {
   376  		return fmt.Errorf("at least one of CDNs must be enabled")
   377  	}
   378  
   379  	return nil
   380  }
   381  
   382  var (
   383  	// ErrGetConfiguration is returned when GetConfiguration fails
   384  	ErrGetConfiguration = errors.New("get configuration")
   385  	// ErrListConfigurations is returned when ListConfigurations fails
   386  	ErrListConfigurations = errors.New("list configurations")
   387  	// ErrCreateConfiguration is returned when CreateConfiguration fails
   388  	ErrCreateConfiguration = errors.New("create configuration")
   389  	// ErrUpdateConfiguration is returned when UpdateConfiguration fails
   390  	ErrUpdateConfiguration = errors.New("update configuration")
   391  	// ErrDeleteConfiguration is returned when DeleteConfiguration fails
   392  	ErrDeleteConfiguration = errors.New("delete configuration")
   393  	// ErrActivateConfiguration is returned when ActivateConfiguration fails
   394  	ErrActivateConfiguration = errors.New("activate configuration")
   395  )
   396  
   397  func (c *cloudwrapper) GetConfiguration(ctx context.Context, params GetConfigurationRequest) (*Configuration, error) {
   398  	logger := c.Log(ctx)
   399  	logger.Debug("GetConfiguration")
   400  
   401  	if err := params.Validate(); err != nil {
   402  		return nil, fmt.Errorf("%s: %w: %s", ErrGetConfiguration, ErrStructValidation, err)
   403  	}
   404  
   405  	uri := fmt.Sprintf("/cloud-wrapper/v1/configurations/%d", params.ConfigID)
   406  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
   407  	if err != nil {
   408  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetConfiguration, err)
   409  	}
   410  
   411  	var result Configuration
   412  	resp, err := c.Exec(req, &result)
   413  	if err != nil {
   414  		return nil, fmt.Errorf("%w: request failed: %s", ErrGetConfiguration, err)
   415  	}
   416  
   417  	if resp.StatusCode != http.StatusOK {
   418  		return nil, fmt.Errorf("%s: %w", ErrGetConfiguration, c.Error(resp))
   419  	}
   420  
   421  	return &result, nil
   422  }
   423  
   424  func (c *cloudwrapper) ListConfigurations(ctx context.Context) (*ListConfigurationsResponse, error) {
   425  	logger := c.Log(ctx)
   426  	logger.Debug("ListConfigurations")
   427  
   428  	uri := "/cloud-wrapper/v1/configurations"
   429  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
   430  	if err != nil {
   431  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrListConfigurations, err)
   432  	}
   433  
   434  	var result ListConfigurationsResponse
   435  	resp, err := c.Exec(req, &result)
   436  	if err != nil {
   437  		return nil, fmt.Errorf("%w: request failed: %s", ErrListConfigurations, err)
   438  	}
   439  
   440  	if resp.StatusCode != http.StatusOK {
   441  		return nil, fmt.Errorf("%s: %w", ErrListConfigurations, c.Error(resp))
   442  	}
   443  
   444  	return &result, nil
   445  }
   446  
   447  func (c *cloudwrapper) CreateConfiguration(ctx context.Context, params CreateConfigurationRequest) (*Configuration, error) {
   448  	logger := c.Log(ctx)
   449  	logger.Debug("CreateConfiguration")
   450  
   451  	if err := params.Validate(); err != nil {
   452  		return nil, fmt.Errorf("%s: %w: %s", ErrCreateConfiguration, ErrStructValidation, err)
   453  	}
   454  
   455  	uri, err := url.Parse("/cloud-wrapper/v1/configurations")
   456  	if err != nil {
   457  		return nil, fmt.Errorf("%w: failed to parse url: %s", ErrCreateConfiguration, err)
   458  	}
   459  
   460  	q := uri.Query()
   461  	q.Add("activate", strconv.FormatBool(params.Activate))
   462  	uri.RawQuery = q.Encode()
   463  
   464  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil)
   465  	if err != nil {
   466  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrCreateConfiguration, err)
   467  	}
   468  
   469  	var result Configuration
   470  	resp, err := c.Exec(req, &result, params.Body)
   471  	if err != nil {
   472  		return nil, fmt.Errorf("%w: request failed: %s", ErrCreateConfiguration, err)
   473  	}
   474  
   475  	if resp.StatusCode != http.StatusCreated {
   476  		return nil, fmt.Errorf("%s: %w", ErrCreateConfiguration, c.Error(resp))
   477  	}
   478  
   479  	return &result, nil
   480  }
   481  
   482  func (c *cloudwrapper) UpdateConfiguration(ctx context.Context, params UpdateConfigurationRequest) (*Configuration, error) {
   483  	logger := c.Log(ctx)
   484  	logger.Debug("UpdateConfiguration")
   485  
   486  	if err := params.Validate(); err != nil {
   487  		return nil, fmt.Errorf("%s: %w: %s", ErrUpdateConfiguration, ErrStructValidation, err)
   488  	}
   489  
   490  	uri, err := url.Parse(fmt.Sprintf("/cloud-wrapper/v1/configurations/%d", params.ConfigID))
   491  	if err != nil {
   492  		return nil, fmt.Errorf("%w: failed to parse url: %s", ErrUpdateConfiguration, err)
   493  	}
   494  
   495  	q := uri.Query()
   496  	q.Add("activate", strconv.FormatBool(params.Activate))
   497  	uri.RawQuery = q.Encode()
   498  
   499  	req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil)
   500  	if err != nil {
   501  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateConfiguration, err)
   502  	}
   503  
   504  	var result Configuration
   505  	resp, err := c.Exec(req, &result, params.Body)
   506  	if err != nil {
   507  		return nil, fmt.Errorf("%w: request failed: %s", ErrUpdateConfiguration, err)
   508  	}
   509  
   510  	if resp.StatusCode != http.StatusOK {
   511  		return nil, fmt.Errorf("%s: %w", ErrUpdateConfiguration, c.Error(resp))
   512  	}
   513  
   514  	return &result, nil
   515  }
   516  
   517  func (c *cloudwrapper) DeleteConfiguration(ctx context.Context, params DeleteConfigurationRequest) error {
   518  	logger := c.Log(ctx)
   519  	logger.Debug("DeleteConfiguration")
   520  
   521  	if err := params.Validate(); err != nil {
   522  		return fmt.Errorf("%s: %w: %s", ErrDeleteConfiguration, ErrStructValidation, err)
   523  	}
   524  
   525  	uri := fmt.Sprintf("/cloud-wrapper/v1/configurations/%d", params.ConfigID)
   526  
   527  	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri, nil)
   528  	if err != nil {
   529  		return fmt.Errorf("%w: failed to create request: %s", ErrDeleteConfiguration, err)
   530  	}
   531  
   532  	resp, err := c.Exec(req, nil)
   533  	if err != nil {
   534  		return fmt.Errorf("%w: request failed: %s", ErrDeleteConfiguration, err)
   535  	}
   536  
   537  	if resp.StatusCode != http.StatusAccepted {
   538  		return fmt.Errorf("%s: %w", ErrDeleteConfiguration, c.Error(resp))
   539  	}
   540  
   541  	return nil
   542  }
   543  
   544  func (c *cloudwrapper) ActivateConfiguration(ctx context.Context, params ActivateConfigurationRequest) error {
   545  	logger := c.Log(ctx)
   546  	logger.Debug("ActivateConfiguration")
   547  
   548  	if err := params.Validate(); err != nil {
   549  		return fmt.Errorf("%s: %w: %s", ErrActivateConfiguration, ErrStructValidation, err)
   550  	}
   551  
   552  	uri := "/cloud-wrapper/v1/configurations/activate"
   553  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, nil)
   554  	if err != nil {
   555  		return fmt.Errorf("%w: failed to create request: %s", ErrActivateConfiguration, err)
   556  	}
   557  
   558  	resp, err := c.Exec(req, nil, params)
   559  	if err != nil {
   560  		return fmt.Errorf("%w: request failed: %s", ErrActivateConfiguration, err)
   561  	}
   562  
   563  	if resp.StatusCode != http.StatusNoContent {
   564  		return fmt.Errorf("%s: %w", ErrActivateConfiguration, c.Error(resp))
   565  	}
   566  
   567  	return nil
   568  }