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

     1  package cloudlets
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr"
    13  
    14  	validation "github.com/go-ozzo/ozzo-validation/v4"
    15  )
    16  
    17  type (
    18  	// LoadBalancerVersions is a cloudlets LoadBalancer version API interface.
    19  	LoadBalancerVersions interface {
    20  		// CreateLoadBalancerVersion creates load balancer version.
    21  		//
    22  		// See: https://techdocs.akamai.com/cloudlets/v2/reference/post-origin-versions
    23  		CreateLoadBalancerVersion(context.Context, CreateLoadBalancerVersionRequest) (*LoadBalancerVersion, error)
    24  
    25  		// GetLoadBalancerVersion gets specific load balancer version by originID and version.
    26  		//
    27  		// See: https://techdocs.akamai.com/cloudlets/v2/reference/get-origin-version
    28  		GetLoadBalancerVersion(context.Context, GetLoadBalancerVersionRequest) (*LoadBalancerVersion, error)
    29  
    30  		// UpdateLoadBalancerVersion updates specific load balancer version by originID and version.
    31  		//
    32  		// See: https://techdocs.akamai.com/cloudlets/v2/reference/put-origin-version
    33  		UpdateLoadBalancerVersion(context.Context, UpdateLoadBalancerVersionRequest) (*LoadBalancerVersion, error)
    34  
    35  		// ListLoadBalancerVersions lists all versions of Origin with type APPLICATION_LOAD_BALANCER.
    36  		//
    37  		// See: https://techdocs.akamai.com/cloudlets/v2/reference/get-origin-versions
    38  		ListLoadBalancerVersions(context.Context, ListLoadBalancerVersionsRequest) ([]LoadBalancerVersion, error)
    39  	}
    40  
    41  	// DataCenter represents the dataCenter field of load balancer version
    42  	DataCenter struct {
    43  		City                          string   `json:"city,omitempty"`
    44  		CloudServerHostHeaderOverride bool     `json:"cloudServerHostHeaderOverride,omitempty"`
    45  		CloudService                  bool     `json:"cloudService"`
    46  		Continent                     string   `json:"continent"`
    47  		Country                       string   `json:"country"`
    48  		Hostname                      string   `json:"hostname,omitempty"`
    49  		Latitude                      *float64 `json:"latitude"`
    50  		LivenessHosts                 []string `json:"livenessHosts,omitempty"`
    51  		Longitude                     *float64 `json:"longitude"`
    52  		OriginID                      string   `json:"originId"`
    53  		Percent                       *float64 `json:"percent"`
    54  		StateOrProvince               *string  `json:"stateOrProvince,omitempty"`
    55  	}
    56  
    57  	// LivenessSettings represents the livenessSettings field of load balancer version
    58  	LivenessSettings struct {
    59  		HostHeader                  string            `json:"hostHeader,omitempty"`
    60  		AdditionalHeaders           map[string]string `json:"additionalHeaders,omitempty"`
    61  		Interval                    int               `json:"interval,omitempty"`
    62  		Path                        string            `json:"path,omitempty"`
    63  		PeerCertificateVerification bool              `json:"peerCertificateVerification,omitempty"`
    64  		Port                        int               `json:"port"`
    65  		Protocol                    string            `json:"protocol"`
    66  		RequestString               string            `json:"requestString,omitempty"`
    67  		ResponseString              string            `json:"responseString,omitempty"`
    68  		Status3xxFailure            bool              `json:"status3xxFailure,omitempty"`
    69  		Status4xxFailure            bool              `json:"status4xxFailure,omitempty"`
    70  		Status5xxFailure            bool              `json:"status5xxFailure,omitempty"`
    71  		Timeout                     float64           `json:"timeout,omitempty"`
    72  	}
    73  
    74  	// BalancingType is a type for BalancingType field
    75  	BalancingType string
    76  
    77  	// LoadBalancerVersion describes the body of the create and update load balancer version request
    78  	LoadBalancerVersion struct {
    79  		BalancingType    BalancingType     `json:"balancingType,omitempty"`
    80  		CreatedBy        string            `json:"createdBy,omitempty"`
    81  		CreatedDate      string            `json:"createdDate,omitempty"`
    82  		DataCenters      []DataCenter      `json:"dataCenters,omitempty"`
    83  		Deleted          bool              `json:"deleted"`
    84  		Description      string            `json:"description,omitempty"`
    85  		Immutable        bool              `json:"immutable"`
    86  		LastModifiedBy   string            `json:"lastModifiedBy,omitempty"`
    87  		LastModifiedDate string            `json:"lastModifiedDate,omitempty"`
    88  		LivenessSettings *LivenessSettings `json:"livenessSettings,omitempty"`
    89  		OriginID         string            `json:"originID,omitempty"`
    90  		Version          int64             `json:"version,omitempty"`
    91  		Warnings         []Warning         `json:"warnings,omitempty"`
    92  	}
    93  
    94  	// CreateLoadBalancerVersionRequest describes the parameters needed to create load balancer version
    95  	CreateLoadBalancerVersionRequest struct {
    96  		OriginID            string
    97  		LoadBalancerVersion LoadBalancerVersion
    98  	}
    99  
   100  	// GetLoadBalancerVersionRequest describes the parameters needed to get load balancer version
   101  	GetLoadBalancerVersionRequest struct {
   102  		OriginID       string
   103  		Version        int64
   104  		ShouldValidate bool
   105  	}
   106  
   107  	// UpdateLoadBalancerVersionRequest describes the parameters needed to update load balancer version
   108  	UpdateLoadBalancerVersionRequest struct {
   109  		OriginID            string
   110  		ShouldValidate      bool
   111  		Version             int64
   112  		LoadBalancerVersion LoadBalancerVersion
   113  	}
   114  
   115  	// ListLoadBalancerVersionsRequest describes the parameters needed to list load balancer versions
   116  	ListLoadBalancerVersionsRequest struct {
   117  		OriginID string
   118  	}
   119  )
   120  
   121  const (
   122  	// BalancingTypeWeighted represents weighted balancing type for load balancer version
   123  	BalancingTypeWeighted BalancingType = "WEIGHTED"
   124  	// BalancingTypePerformance represents performance balancing type for load balancer version
   125  	BalancingTypePerformance BalancingType = "PERFORMANCE"
   126  )
   127  
   128  var (
   129  	// ErrCreateLoadBalancerVersion is returned when CreateLoadBalancerVersion fails
   130  	ErrCreateLoadBalancerVersion = errors.New("create origin version")
   131  	// ErrGetLoadBalancerVersion is returned when GetLoadBalancerVersion fails
   132  	ErrGetLoadBalancerVersion = errors.New("get origin version")
   133  	// ErrUpdateLoadBalancerVersion is returned when UpdateLoadBalancerVersion fails
   134  	ErrUpdateLoadBalancerVersion = errors.New("update origin version")
   135  	// ErrListLoadBalancerVersions is returned when ListLoadBalancerVersions fails
   136  	ErrListLoadBalancerVersions = errors.New("list origin versions")
   137  )
   138  
   139  // Validate validates DataCenter
   140  func (v DataCenter) Validate() error {
   141  	return validation.Errors{
   142  		"Continent": validation.Validate(v.Continent, validation.Required, validation.In("AF", "AS", "EU", "NA", "OC", "OT", "SA").Error(
   143  			fmt.Sprintf("value '%s' is invalid. Must be one of: 'AF', 'AS', 'EU', 'NA', 'OC', 'OT' or 'SA'", (&v).Continent))),
   144  		"Country":   validation.Validate(v.Country, validation.Required, validation.Length(2, 2)),
   145  		"Hostname":  validation.Validate(v.Hostname, validation.Length(0, 256)),
   146  		"Latitude":  validation.Validate(v.Latitude, validation.NotNil, validation.Min(-180.0), validation.Max(180.0)),
   147  		"Longitude": validation.Validate(v.Longitude, validation.NotNil, validation.Min(-180.0), validation.Max(180.0)),
   148  		"OriginID":  validation.Validate(v.OriginID, validation.Required, validation.Length(1, 128)),
   149  		"Percent":   validation.Validate(v.Percent, validation.NotNil, validation.Min(0.0), validation.Max(100.0)),
   150  	}.Filter()
   151  }
   152  
   153  // generateHostHeaderRules generates case insensitive validation rules for host header
   154  // its required because schema requires that host header value contains at least 1 char
   155  // but it doesnt put such requirement on other headers, so these two cases need to be considered separately
   156  func generateHostHeaderRules(headers map[string]string) []*validation.KeyRules {
   157  	var hostRules []*validation.KeyRules
   158  
   159  	for k := range headers {
   160  		if strings.ToLower(k) == "host" {
   161  			hostRules = append(hostRules, validation.Key(k, validation.Length(1, 256)))
   162  		}
   163  	}
   164  	return hostRules
   165  }
   166  
   167  // Validate validates LivenessSettings
   168  func (v LivenessSettings) Validate() error {
   169  	return validation.Errors{
   170  		"HostHeader":        validation.Validate(v.HostHeader, validation.Length(1, 256)),
   171  		"AdditionalHeaders": validation.Validate(v.AdditionalHeaders, validation.Map(generateHostHeaderRules(v.AdditionalHeaders)...).AllowExtraKeys()),
   172  		"Interval":          validation.Validate(v.Interval, validation.Min(10), validation.Max(3600)),
   173  		"Path": validation.Validate(v.Path,
   174  			validation.When(v.Protocol == "HTTP" || v.Protocol == "HTTPS", validation.Required, validation.Length(1, 256)),
   175  		),
   176  		"Port": validation.Validate(v.Port, validation.Required, validation.Min(1), validation.Max(65535)),
   177  		"Protocol": validation.Validate(v.Protocol, validation.Required, validation.In("HTTP", "HTTPS", "TCP", "TCPS").Error(
   178  			fmt.Sprintf("value '%s' is invalid. Must be one of: 'HTTP', 'HTTPS', 'TCP' or 'TCPS'", (&v).Protocol))),
   179  		"RequestString": validation.Validate(v.RequestString,
   180  			validation.When(v.Protocol == "TCP" || v.Protocol == "TCPS", validation.Required),
   181  		),
   182  		"ResponseString": validation.Validate(v.ResponseString,
   183  			validation.When(v.Protocol == "TCP" || v.Protocol == "TCPS", validation.Required),
   184  		),
   185  		"Timeout": validation.Validate(v.Timeout, validation.Min(0.001), validation.Max(60.0)),
   186  	}.Filter()
   187  }
   188  
   189  // Validate validates Warning
   190  func (v Warning) Validate() error {
   191  	return validation.Errors{
   192  		"Detail":      validation.Validate(v.Detail, validation.Required),
   193  		"JSONPointer": validation.Validate(v.JSONPointer, validation.Length(0, 128)),
   194  		"Title":       validation.Validate(v.Title, validation.Required),
   195  		"Type":        validation.Validate(v.Type, validation.Required),
   196  	}
   197  }
   198  
   199  // Validate validates LoadBalancerVersion
   200  func (v LoadBalancerVersion) Validate() error {
   201  	return validation.Errors{
   202  		"BalancingType": validation.Validate(v.BalancingType, validation.In(BalancingTypeWeighted, BalancingTypePerformance).Error(
   203  			fmt.Sprintf("value '%s' is invalid. Must be one of: 'WEIGHTED', 'PERFORMANCE' or '' (empty)", (&v).BalancingType))),
   204  		"CreatedDate":      validation.Validate(v.CreatedDate, validation.Date(time.RFC3339)),
   205  		"DataCenters":      validation.Validate(v.DataCenters, validation.Length(1, 199)),
   206  		"LastModifiedDate": validation.Validate(v.LastModifiedDate, validation.Date(time.RFC3339)),
   207  		"LivenessSettings": validation.Validate(v.LivenessSettings),
   208  		"OriginID":         validation.Validate(v.OriginID, validation.Length(2, 62)),
   209  		"Version":          validation.Validate(v.Version, validation.Min(0)),
   210  		"Warnings":         validation.Validate(v.Warnings),
   211  	}.Filter()
   212  }
   213  
   214  // Validate validates CreateLoadBalancerVersionRequest
   215  func (v CreateLoadBalancerVersionRequest) Validate() error {
   216  	errs := validation.Errors{
   217  		"OriginID":            validation.Validate(v.OriginID, validation.Length(2, 62)),
   218  		"LoadBalancerVersion": validation.Validate(v.LoadBalancerVersion),
   219  	}
   220  	return edgegriderr.ParseValidationErrors(errs)
   221  }
   222  
   223  // Validate validates GetLoadBalancerVersionRequest
   224  func (v GetLoadBalancerVersionRequest) Validate() error {
   225  	errs := validation.Errors{
   226  		"OriginID": validation.Validate(v.OriginID, validation.Length(2, 62)),
   227  		"Version":  validation.Validate(v.Version, validation.Min(0)),
   228  	}
   229  	return edgegriderr.ParseValidationErrors(errs)
   230  }
   231  
   232  // Validate validates UpdateLoadBalancerVersionRequest
   233  func (v UpdateLoadBalancerVersionRequest) Validate() error {
   234  	errs := validation.Errors{
   235  		"OriginID":            validation.Validate(v.OriginID, validation.Length(2, 62)),
   236  		"Version":             validation.Validate(v.Version, validation.Min(0)),
   237  		"LoadBalancerVersion": validation.Validate(v.LoadBalancerVersion),
   238  	}
   239  	return edgegriderr.ParseValidationErrors(errs)
   240  }
   241  
   242  // Validate validates ListLoadBalancerVersionsRequest
   243  func (v ListLoadBalancerVersionsRequest) Validate() error {
   244  	errs := validation.Errors{
   245  		"OriginID": validation.Validate(v.OriginID, validation.Required, validation.Length(2, 62)),
   246  	}
   247  	return edgegriderr.ParseValidationErrors(errs)
   248  }
   249  
   250  func (c *cloudlets) CreateLoadBalancerVersion(ctx context.Context, params CreateLoadBalancerVersionRequest) (*LoadBalancerVersion, error) {
   251  	logger := c.Log(ctx)
   252  	logger.Debug("CreateLoadBalancerVersion")
   253  
   254  	if err := params.Validate(); err != nil {
   255  		return nil, fmt.Errorf("%s: %w:\n%s", ErrCreateLoadBalancerVersion, ErrStructValidation, err)
   256  	}
   257  
   258  	uri, err := url.Parse(fmt.Sprintf("/cloudlets/api/v2/origins/%s/versions", params.OriginID))
   259  	if err != nil {
   260  		return nil, fmt.Errorf("%w: failed to parse url: %s", ErrCreateLoadBalancerVersion, err)
   261  	}
   262  
   263  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri.String(), nil)
   264  	if err != nil {
   265  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrCreateLoadBalancerVersion, err)
   266  	}
   267  
   268  	var result LoadBalancerVersion
   269  	resp, err := c.Exec(req, &result, params.LoadBalancerVersion)
   270  	if err != nil {
   271  		return nil, fmt.Errorf("%w: request failed: %s", ErrCreateLoadBalancerVersion, err)
   272  	}
   273  
   274  	if resp.StatusCode != http.StatusCreated {
   275  		return nil, fmt.Errorf("%s: %w", ErrCreateLoadBalancerVersion, c.Error(resp))
   276  	}
   277  
   278  	return &result, nil
   279  }
   280  
   281  func (c *cloudlets) GetLoadBalancerVersion(ctx context.Context, params GetLoadBalancerVersionRequest) (*LoadBalancerVersion, error) {
   282  	logger := c.Log(ctx)
   283  	logger.Debug("GetLoadBalancerVersion")
   284  
   285  	if err := params.Validate(); err != nil {
   286  		return nil, fmt.Errorf("%s: %w:\n%s", ErrGetLoadBalancerVersion, ErrStructValidation, err)
   287  	}
   288  
   289  	uri, err := url.Parse(fmt.Sprintf("/cloudlets/api/v2/origins/%s/versions/%d", params.OriginID, params.Version))
   290  	if err != nil {
   291  		return nil, fmt.Errorf("%w: failed to parse url: %s", ErrGetLoadBalancerVersion, err)
   292  	}
   293  
   294  	if params.ShouldValidate {
   295  		q := uri.Query()
   296  		q.Add("validate", "true")
   297  		uri.RawQuery = q.Encode()
   298  	}
   299  
   300  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil)
   301  	if err != nil {
   302  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetLoadBalancerVersion, err)
   303  	}
   304  
   305  	var result LoadBalancerVersion
   306  	resp, err := c.Exec(req, &result)
   307  	if err != nil {
   308  		return nil, fmt.Errorf("%w: request failed: %s", ErrGetLoadBalancerVersion, err)
   309  	}
   310  
   311  	if resp.StatusCode != http.StatusOK {
   312  		return nil, fmt.Errorf("%s: %w", ErrGetLoadBalancerVersion, c.Error(resp))
   313  	}
   314  
   315  	return &result, nil
   316  }
   317  
   318  func (c *cloudlets) UpdateLoadBalancerVersion(ctx context.Context, params UpdateLoadBalancerVersionRequest) (*LoadBalancerVersion, error) {
   319  	logger := c.Log(ctx)
   320  	logger.Debug("UpdateLoadBalancerVersion")
   321  
   322  	if err := params.Validate(); err != nil {
   323  		return nil, fmt.Errorf("%s: %w:\n%s", ErrUpdateLoadBalancerVersion, ErrStructValidation, err)
   324  	}
   325  
   326  	uri, err := url.Parse(fmt.Sprintf("/cloudlets/api/v2/origins/%s/versions/%d", params.OriginID, params.Version))
   327  	if err != nil {
   328  		return nil, fmt.Errorf("%w: failed to parse url: %s", ErrUpdateLoadBalancerVersion, err)
   329  	}
   330  
   331  	if params.ShouldValidate {
   332  		q := uri.Query()
   333  		q.Add("validate", "true")
   334  		uri.RawQuery = q.Encode()
   335  	}
   336  
   337  	req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri.String(), nil)
   338  	if err != nil {
   339  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpdateLoadBalancerVersion, err)
   340  	}
   341  
   342  	var result LoadBalancerVersion
   343  	resp, err := c.Exec(req, &result, params.LoadBalancerVersion)
   344  	if err != nil {
   345  		return nil, fmt.Errorf("%w: request failed: %s", ErrUpdateLoadBalancerVersion, err)
   346  	}
   347  
   348  	if resp.StatusCode != http.StatusOK {
   349  		return nil, fmt.Errorf("%s: %w", ErrUpdateLoadBalancerVersion, c.Error(resp))
   350  	}
   351  
   352  	return &result, nil
   353  }
   354  
   355  func (c *cloudlets) ListLoadBalancerVersions(ctx context.Context, params ListLoadBalancerVersionsRequest) ([]LoadBalancerVersion, error) {
   356  	logger := c.Log(ctx)
   357  	logger.Debug("ListLoadBalancerVersions")
   358  
   359  	if err := params.Validate(); err != nil {
   360  		return nil, fmt.Errorf("%s: %w:\n%s", ErrListLoadBalancerVersions, ErrStructValidation, err)
   361  	}
   362  
   363  	uri, err := url.Parse(fmt.Sprintf("/cloudlets/api/v2/origins/%s/versions?includeModel=true", params.OriginID))
   364  	if err != nil {
   365  		return nil, fmt.Errorf("%w: failed to parse url: %s", ErrListLoadBalancerVersions, err)
   366  	}
   367  
   368  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil)
   369  	if err != nil {
   370  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrListLoadBalancerVersions, err)
   371  	}
   372  
   373  	var result []LoadBalancerVersion
   374  	resp, err := c.Exec(req, &result)
   375  	if err != nil {
   376  		return nil, fmt.Errorf("%w: request failed: %s", ErrListLoadBalancerVersions, err)
   377  	}
   378  
   379  	if resp.StatusCode != http.StatusOK {
   380  		return nil, fmt.Errorf("%s: %w", ErrListLoadBalancerVersions, c.Error(resp))
   381  	}
   382  
   383  	return result, nil
   384  }