diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action.go index 6ab2f1095dac74773ec05f93e6cb31fba321566d..7943a8c15499e18f1ba06ed708d63f0a7ad32709 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action.go @@ -6,6 +6,7 @@ import ( "net/url" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -54,9 +55,21 @@ const ( type ActionError struct { Code string Message string + + action *Action +} + +// Action returns the [Action] that triggered the error if available. +func (e ActionError) Action() *Action { + return e.action } func (e ActionError) Error() string { + action := e.Action() + if action != nil { + // For easier debugging, the error string contains the Action ID. + return fmt.Sprintf("%s (%s, %d)", e.Message, e.Code, action.ID) + } return fmt.Sprintf("%s (%s)", e.Message, e.Code) } @@ -65,6 +78,7 @@ func (a *Action) Error() error { return ActionError{ Code: a.ErrorCode, Message: a.ErrorMessage, + action: a, } } return nil @@ -111,11 +125,15 @@ func (c *ActionClient) List(ctx context.Context, opts ActionListOpts) ([]*Action } // All returns all actions. +// +// Deprecated: It is required to pass in a list of IDs since 30 January 2025. Please use [ActionClient.AllWithOpts] instead. func (c *ActionClient) All(ctx context.Context) ([]*Action, error) { return c.action.All(ctx, ActionListOpts{ListOpts: ListOpts{PerPage: 50}}) } // AllWithOpts returns all actions for the given options. +// +// It is required to set [ActionListOpts.ID]. Any other fields set in the opts are ignored. func (c *ActionClient) AllWithOpts(ctx context.Context, opts ActionListOpts) ([]*Action, error) { return c.action.All(ctx, opts) } @@ -136,20 +154,19 @@ func (c *ResourceActionClient) getBaseURL() string { // GetByID retrieves an action by its ID. If the action does not exist, nil is returned. func (c *ResourceActionClient) GetByID(ctx context.Context, id int64) (*Action, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("%s/actions/%d", c.getBaseURL(), id), nil) - if err != nil { - return nil, nil, err - } + opPath := c.getBaseURL() + "/actions/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) - var body schema.ActionGetResponse - resp, err := c.client.Do(req, &body) + reqPath := fmt.Sprintf(opPath, id) + + respBody, resp, err := getRequest[schema.ActionGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return ActionFromSchema(body.Action), resp, nil + return ActionFromSchema(respBody.Action), resp, nil } // List returns a list of actions for a specific page. @@ -157,44 +174,23 @@ func (c *ResourceActionClient) GetByID(ctx context.Context, id int64) (*Action, // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *ResourceActionClient) List(ctx context.Context, opts ActionListOpts) ([]*Action, *Response, error) { - req, err := c.client.NewRequest( - ctx, - "GET", - fmt.Sprintf("%s/actions?%s", c.getBaseURL(), opts.values().Encode()), - nil, - ) - if err != nil { - return nil, nil, err - } + opPath := c.getBaseURL() + "/actions?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) - var body schema.ActionListResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.ActionListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - actions := make([]*Action, 0, len(body.Actions)) - for _, i := range body.Actions { - actions = append(actions, ActionFromSchema(i)) + return nil, resp, err } - return actions, resp, nil + + return allFromSchemaFunc(respBody.Actions, ActionFromSchema), resp, nil } // All returns all actions for the given options. func (c *ResourceActionClient) All(ctx context.Context, opts ActionListOpts) ([]*Action, error) { - allActions := []*Action{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*Action, *Response, error) { opts.Page = page - actions, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allActions = append(allActions, actions...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allActions, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_waiter.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_waiter.go index ebfe8ef4eb3e73a5a0424a6118777f9e9d5b25e9..50f5bba7723402e0fe5d6b0d3277e46eedb92657 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_waiter.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_waiter.go @@ -16,11 +16,14 @@ type ActionWaiter interface { var _ ActionWaiter = (*ActionClient)(nil) // WaitForFunc waits until all actions are completed by polling the API at the interval -// defined by [WithPollBackoffFunc]. An action is considered as complete when its status is +// defined by [WithPollOpts]. An action is considered as complete when its status is // either [ActionStatusSuccess] or [ActionStatusError]. // // The handleUpdate callback is called every time an action is updated. func (c *ActionClient) WaitForFunc(ctx context.Context, handleUpdate func(update *Action) error, actions ...*Action) error { + // Filter out nil actions + actions = slices.DeleteFunc(actions, func(a *Action) bool { return a == nil }) + running := make(map[int64]struct{}, len(actions)) for _, action := range actions { if action.Status == ActionStatusRunning { @@ -48,18 +51,19 @@ func (c *ActionClient) WaitForFunc(ctx context.Context, handleUpdate func(update retries++ } - opts := ActionListOpts{ - Sort: []string{"status", "id"}, - ID: make([]int64, 0, len(running)), - } - for actionID := range running { - opts.ID = append(opts.ID, actionID) - } - slices.Sort(opts.ID) + updates := make([]*Action, 0, len(running)) + for runningIDsChunk := range slices.Chunk(slices.Sorted(maps.Keys(running)), 25) { + opts := ActionListOpts{ + Sort: []string{"status", "id"}, + ID: runningIDsChunk, + } + + updatesChunk, err := c.AllWithOpts(ctx, opts) + if err != nil { + return err + } - updates, err := c.AllWithOpts(ctx, opts) - if err != nil { - return err + updates = append(updates, updatesChunk...) } if len(updates) != len(running) { @@ -95,7 +99,7 @@ func (c *ActionClient) WaitForFunc(ctx context.Context, handleUpdate func(update } // WaitFor waits until all actions succeed by polling the API at the interval defined by -// [WithPollBackoffFunc]. An action is considered as succeeded when its status is either +// [WithPollOpts]. An action is considered as succeeded when its status is either // [ActionStatusSuccess]. // // If a single action fails, the function will stop waiting and the error set in the diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_watch.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_watch.go index db3464f11cacc69fdd696681d25e508c094b9ed7..58c2eb878d584d8df7b0c19f26bb564ea848f5ba 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_watch.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/action_watch.go @@ -21,7 +21,7 @@ import ( // timeout, use the [context.Context]. Once the method has stopped watching, // both returned channels are closed. // -// WatchOverallProgress uses the [WithPollBackoffFunc] of the [Client] to wait +// WatchOverallProgress uses the [WithPollOpts] of the [Client] to wait // until sending the next request. // // Deprecated: WatchOverallProgress is deprecated, use [WaitForFunc] instead. @@ -86,7 +86,7 @@ func (c *ActionClient) WatchOverallProgress(ctx context.Context, actions []*Acti // timeout, use the [context.Context]. Once the method has stopped watching, // both returned channels are closed. // -// WatchProgress uses the [WithPollBackoffFunc] of the [Client] to wait until +// WatchProgress uses the [WithPollOpts] of the [Client] to wait until // sending the next request. // // Deprecated: WatchProgress is deprecated, use [WaitForFunc] instead. diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/certificate.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/certificate.go index e6ffa50cb62a5ed03823ac5e9275317fb6c8a016..97bb572882b6496b7345a6d1c2a19d580f10333e 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/certificate.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/certificate.go @@ -1,15 +1,12 @@ package hcloud import ( - "bytes" "context" - "encoding/json" - "errors" "fmt" "net/url" - "strconv" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -98,41 +95,32 @@ type CertificateClient struct { // GetByID retrieves a Certificate by its ID. If the Certificate does not exist, nil is returned. func (c *CertificateClient) GetByID(ctx context.Context, id int64) (*Certificate, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/certificates/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/certificates/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.CertificateGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.CertificateGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return CertificateFromSchema(body.Certificate), resp, nil + return CertificateFromSchema(respBody.Certificate), resp, nil } // GetByName retrieves a Certificate by its name. If the Certificate does not exist, nil is returned. func (c *CertificateClient) GetByName(ctx context.Context, name string) (*Certificate, *Response, error) { - if name == "" { - return nil, nil, nil - } - Certificate, response, err := c.List(ctx, CertificateListOpts{Name: name}) - if len(Certificate) == 0 { - return nil, response, err - } - return Certificate[0], response, err + return firstByName(name, func() ([]*Certificate, *Response, error) { + return c.List(ctx, CertificateListOpts{Name: name}) + }) } // Get retrieves a Certificate by its ID if the input can be parsed as an integer, otherwise it // retrieves a Certificate by its name. If the Certificate does not exist, nil is returned. func (c *CertificateClient) Get(ctx context.Context, idOrName string) (*Certificate, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByName(ctx, idOrName) + return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName) } // CertificateListOpts specifies options for listing Certificates. @@ -158,22 +146,17 @@ func (l CertificateListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *CertificateClient) List(ctx context.Context, opts CertificateListOpts) ([]*Certificate, *Response, error) { - path := "/certificates?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/certificates?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) - var body schema.CertificateListResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.CertificateListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - Certificates := make([]*Certificate, 0, len(body.Certificates)) - for _, s := range body.Certificates { - Certificates = append(Certificates, CertificateFromSchema(s)) + return nil, resp, err } - return Certificates, resp, nil + + return allFromSchemaFunc(respBody.Certificates, CertificateFromSchema), resp, nil } // All returns all Certificates. @@ -183,22 +166,10 @@ func (c *CertificateClient) All(ctx context.Context) ([]*Certificate, error) { // AllWithOpts returns all Certificates for the given options. func (c *CertificateClient) AllWithOpts(ctx context.Context, opts CertificateListOpts) ([]*Certificate, error) { - allCertificates := []*Certificate{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*Certificate, *Response, error) { opts.Page = page - Certificates, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allCertificates = append(allCertificates, Certificates...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allCertificates, nil } // CertificateCreateOpts specifies options for creating a new Certificate. @@ -214,7 +185,7 @@ type CertificateCreateOpts struct { // Validate checks if options are valid. func (o CertificateCreateOpts) Validate() error { if o.Name == "" { - return errors.New("missing name") + return missingField(o, "Name") } switch o.Type { case "", CertificateTypeUploaded: @@ -222,23 +193,23 @@ func (o CertificateCreateOpts) Validate() error { case CertificateTypeManaged: return o.validateManaged() default: - return fmt.Errorf("invalid type: %s", o.Type) + return invalidFieldValue(o, "Type", o.Type) } } func (o CertificateCreateOpts) validateManaged() error { if len(o.DomainNames) == 0 { - return errors.New("no domain names") + return missingField(o, "DomainNames") } return nil } func (o CertificateCreateOpts) validateUploaded() error { if o.Certificate == "" { - return errors.New("missing certificate") + return missingField(o, "Certificate") } if o.PrivateKey == "" { - return errors.New("missing private key") + return missingField(o, "PrivateKey") } return nil } @@ -249,7 +220,7 @@ func (o CertificateCreateOpts) validateUploaded() error { // CreateCertificate to create such certificates. func (c *CertificateClient) Create(ctx context.Context, opts CertificateCreateOpts) (*Certificate, *Response, error) { if !(opts.Type == "" || opts.Type == CertificateTypeUploaded) { - return nil, nil, fmt.Errorf("invalid certificate type: %s", opts.Type) + return nil, nil, invalidFieldValue(opts, "Type", opts.Type) } result, resp, err := c.CreateCertificate(ctx, opts) if err != nil { @@ -262,16 +233,20 @@ func (c *CertificateClient) Create(ctx context.Context, opts CertificateCreateOp func (c *CertificateClient) CreateCertificate( ctx context.Context, opts CertificateCreateOpts, ) (CertificateCreateResult, *Response, error) { - var ( - action *Action - reqBody schema.CertificateCreateRequest - ) + const opPath = "/certificates" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := opPath + + result := CertificateCreateResult{} if err := opts.Validate(); err != nil { - return CertificateCreateResult{}, nil, err + return result, nil, err } - reqBody.Name = opts.Name + reqBody := schema.CertificateCreateRequest{ + Name: opts.Name, + } switch opts.Type { case "", CertificateTypeUploaded: @@ -282,32 +257,24 @@ func (c *CertificateClient) CreateCertificate( reqBody.Type = string(CertificateTypeManaged) reqBody.DomainNames = opts.DomainNames default: - return CertificateCreateResult{}, nil, fmt.Errorf("invalid certificate type: %v", opts.Type) + return result, nil, invalidFieldValue(opts, "Type", opts.Type) } if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return CertificateCreateResult{}, nil, err - } - req, err := c.client.NewRequest(ctx, "POST", "/certificates", bytes.NewReader(reqBodyData)) - if err != nil { - return CertificateCreateResult{}, nil, err - } - respBody := schema.CertificateCreateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.CertificateCreateResponse](ctx, c.client, reqPath, reqBody) if err != nil { - return CertificateCreateResult{}, resp, err + return result, resp, err } - cert := CertificateFromSchema(respBody.Certificate) + + result.Certificate = CertificateFromSchema(respBody.Certificate) if respBody.Action != nil { - action = ActionFromSchema(*respBody.Action) + result.Action = ActionFromSchema(*respBody.Action) } - return CertificateCreateResult{Certificate: cert, Action: action}, resp, nil + return result, resp, nil } // CertificateUpdateOpts specifies options for updating a Certificate. @@ -318,6 +285,11 @@ type CertificateUpdateOpts struct { // Update updates a Certificate. func (c *CertificateClient) Update(ctx context.Context, certificate *Certificate, opts CertificateUpdateOpts) (*Certificate, *Response, error) { + const opPath = "/certificates/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, certificate.ID) + reqBody := schema.CertificateUpdateRequest{} if opts.Name != "" { reqBody.Name = &opts.Name @@ -325,46 +297,36 @@ func (c *CertificateClient) Update(ctx context.Context, certificate *Certificate if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/certificates/%d", certificate.ID) - req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.CertificateUpdateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := putRequest[schema.CertificateUpdateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return CertificateFromSchema(respBody.Certificate), resp, nil } // Delete deletes a certificate. func (c *CertificateClient) Delete(ctx context.Context, certificate *Certificate) (*Response, error) { - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/certificates/%d", certificate.ID), nil) - if err != nil { - return nil, err - } - return c.client.Do(req, nil) + const opPath = "/certificates/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, certificate.ID) + + return deleteRequestNoResult(ctx, c.client, reqPath) } // RetryIssuance retries the issuance of a failed managed certificate. func (c *CertificateClient) RetryIssuance(ctx context.Context, certificate *Certificate) (*Action, *Response, error) { - var respBody schema.CertificateIssuanceRetryResponse + const opPath = "/certificates/%d/actions/retry" + ctx = ctxutil.SetOpPath(ctx, opPath) - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("/certificates/%d/actions/retry", certificate.ID), nil) - if err != nil { - return nil, nil, err - } - resp, err := c.client.Do(req, &respBody) + reqPath := fmt.Sprintf(opPath, certificate.ID) + + respBody, resp, err := postRequest[schema.CertificateIssuanceRetryResponse](ctx, c.client, reqPath, nil) if err != nil { - return nil, nil, err + return nil, resp, err } - action := ActionFromSchema(respBody.Action) - return action, resp, nil + + return ActionFromSchema(respBody.Action), resp, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go index 9a7da731f1afb568a74c1ea738eb15d8d5fdb9ad..3a1a66263a293d3e08c01cd881f0568976bffefc 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go @@ -3,13 +3,12 @@ package hcloud import ( "bytes" "context" - "encoding/json" "errors" "fmt" "io" "math" + "math/rand" "net/http" - "net/http/httputil" "net/url" "strconv" "strings" @@ -19,7 +18,6 @@ import ( "golang.org/x/net/http/httpguts" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation" - "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) // Endpoint is the base URL of the API. @@ -43,13 +41,43 @@ func ConstantBackoff(d time.Duration) BackoffFunc { } // ExponentialBackoff returns a BackoffFunc which implements an exponential -// backoff. -// It uses the formula: +// backoff, truncated to 60 seconds. +// See [ExponentialBackoffWithOpts] for more details. +func ExponentialBackoff(multiplier float64, base time.Duration) BackoffFunc { + return ExponentialBackoffWithOpts(ExponentialBackoffOpts{ + Base: base, + Multiplier: multiplier, + Cap: time.Minute, + }) +} + +// ExponentialBackoffOpts defines the options used by [ExponentialBackoffWithOpts]. +type ExponentialBackoffOpts struct { + Base time.Duration + Multiplier float64 + Cap time.Duration + Jitter bool +} + +// ExponentialBackoffWithOpts returns a BackoffFunc which implements an exponential +// backoff, truncated to a maximum, and an optional full jitter. // -// b^retries * d -func ExponentialBackoff(b float64, d time.Duration) BackoffFunc { +// See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ +func ExponentialBackoffWithOpts(opts ExponentialBackoffOpts) BackoffFunc { + baseSeconds := opts.Base.Seconds() + capSeconds := opts.Cap.Seconds() + return func(retries int) time.Duration { - return time.Duration(math.Pow(b, float64(retries))) * d + // Exponential backoff + backoff := baseSeconds * math.Pow(opts.Multiplier, float64(retries)) + // Cap backoff + backoff = math.Min(capSeconds, backoff) + // Add jitter + if opts.Jitter { + backoff = ((backoff - baseSeconds) * rand.Float64()) + baseSeconds // #nosec G404 + } + + return time.Duration(backoff * float64(time.Second)) } } @@ -58,7 +86,8 @@ type Client struct { endpoint string token string tokenValid bool - backoffFunc BackoffFunc + retryBackoffFunc BackoffFunc + retryMaxRetries int pollBackoffFunc BackoffFunc httpClient *http.Client applicationName string @@ -66,6 +95,7 @@ type Client struct { userAgent string debugWriter io.Writer instrumentationRegistry prometheus.Registerer + handler handler Action ActionClient Certificate CertificateClient @@ -110,30 +140,73 @@ func WithToken(token string) ClientOption { // polling from the API. // // Deprecated: Setting the poll interval is deprecated, you can now configure -// [WithPollBackoffFunc] with a [ConstantBackoff] to get the same results. To +// [WithPollOpts] with a [ConstantBackoff] to get the same results. To // migrate your code, replace your usage like this: // // // before // hcloud.WithPollInterval(2 * time.Second) // // now -// hcloud.WithPollBackoffFunc(hcloud.ConstantBackoff(2 * time.Second)) +// hcloud.WithPollOpts(hcloud.PollOpts{ +// BackoffFunc: hcloud.ConstantBackoff(2 * time.Second), +// }) func WithPollInterval(pollInterval time.Duration) ClientOption { - return WithPollBackoffFunc(ConstantBackoff(pollInterval)) + return WithPollOpts(PollOpts{ + BackoffFunc: ConstantBackoff(pollInterval), + }) } // WithPollBackoffFunc configures a Client to use the specified backoff // function when polling from the API. +// +// Deprecated: WithPollBackoffFunc is deprecated, use [WithPollOpts] instead. func WithPollBackoffFunc(f BackoffFunc) ClientOption { + return WithPollOpts(PollOpts{ + BackoffFunc: f, + }) +} + +// PollOpts defines the options used by [WithPollOpts]. +type PollOpts struct { + BackoffFunc BackoffFunc +} + +// WithPollOpts configures a Client to use the specified options when polling from the API. +// +// If [PollOpts.BackoffFunc] is nil, the existing backoff function will be preserved. +func WithPollOpts(opts PollOpts) ClientOption { return func(client *Client) { - client.pollBackoffFunc = f + if opts.BackoffFunc != nil { + client.pollBackoffFunc = opts.BackoffFunc + } } } // WithBackoffFunc configures a Client to use the specified backoff function. // The backoff function is used for retrying HTTP requests. +// +// Deprecated: WithBackoffFunc is deprecated, use [WithRetryOpts] instead. func WithBackoffFunc(f BackoffFunc) ClientOption { return func(client *Client) { - client.backoffFunc = f + client.retryBackoffFunc = f + } +} + +// RetryOpts defines the options used by [WithRetryOpts]. +type RetryOpts struct { + BackoffFunc BackoffFunc + MaxRetries int +} + +// WithRetryOpts configures a Client to use the specified options when retrying API +// requests. +// +// If [RetryOpts.BackoffFunc] is nil, the existing backoff function will be preserved. +func WithRetryOpts(opts RetryOpts) ClientOption { + return func(client *Client) { + if opts.BackoffFunc != nil { + client.retryBackoffFunc = opts.BackoffFunc + } + client.retryMaxRetries = opts.MaxRetries } } @@ -172,10 +245,18 @@ func WithInstrumentation(registry prometheus.Registerer) ClientOption { // NewClient creates a new client. func NewClient(options ...ClientOption) *Client { client := &Client{ - endpoint: Endpoint, - tokenValid: true, - httpClient: &http.Client{}, - backoffFunc: ExponentialBackoff(2, 500*time.Millisecond), + endpoint: Endpoint, + tokenValid: true, + httpClient: &http.Client{}, + + retryBackoffFunc: ExponentialBackoffWithOpts(ExponentialBackoffOpts{ + Base: time.Second, + Multiplier: 2, + Cap: time.Minute, + Jitter: true, + }), + retryMaxRetries: 5, + pollBackoffFunc: ConstantBackoff(500 * time.Millisecond), } @@ -186,9 +267,11 @@ func NewClient(options ...ClientOption) *Client { client.buildUserAgent() if client.instrumentationRegistry != nil { i := instrumentation.New("api", client.instrumentationRegistry) - client.httpClient.Transport = i.InstrumentedRoundTripper() + client.httpClient.Transport = i.InstrumentedRoundTripper(client.httpClient.Transport) } + client.handler = assembleHandlerChain(client) + client.Action = ActionClient{action: &ResourceActionClient{client: client}} client.Datacenter = DatacenterClient{client: client} client.FloatingIP = FloatingIPClient{client: client, Action: &ResourceActionClient{client: client, resource: "floating_ips"}} @@ -238,97 +321,8 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re // Do performs an HTTP request against the API. // v can be nil, an io.Writer to write the response body to or a pointer to // a struct to json.Unmarshal the response to. -func (c *Client) Do(r *http.Request, v interface{}) (*Response, error) { - var retries int - var body []byte - var err error - if r.ContentLength > 0 { - body, err = io.ReadAll(r.Body) - if err != nil { - r.Body.Close() - return nil, err - } - r.Body.Close() - } - for { - if r.ContentLength > 0 { - r.Body = io.NopCloser(bytes.NewReader(body)) - } - - if c.debugWriter != nil { - dumpReq, err := dumpRequest(r) - if err != nil { - return nil, err - } - fmt.Fprintf(c.debugWriter, "--- Request:\n%s\n\n", dumpReq) - } - - resp, err := c.httpClient.Do(r) - if err != nil { - return nil, err - } - response := &Response{Response: resp} - body, err := io.ReadAll(resp.Body) - if err != nil { - resp.Body.Close() - return response, err - } - resp.Body.Close() - resp.Body = io.NopCloser(bytes.NewReader(body)) - - if c.debugWriter != nil { - dumpResp, err := httputil.DumpResponse(resp, true) - if err != nil { - return nil, err - } - fmt.Fprintf(c.debugWriter, "--- Response:\n%s\n\n", dumpResp) - } - - if err = response.readMeta(body); err != nil { - return response, fmt.Errorf("hcloud: error reading response meta data: %s", err) - } - - if response.StatusCode >= 400 && response.StatusCode <= 599 { - err = errorFromResponse(response, body) - if err == nil { - err = fmt.Errorf("hcloud: server responded with status code %d", resp.StatusCode) - } else if IsError(err, ErrorCodeConflict) { - c.backoff(retries) - retries++ - continue - } - return response, err - } - if v != nil { - if w, ok := v.(io.Writer); ok { - _, err = io.Copy(w, bytes.NewReader(body)) - } else { - err = json.Unmarshal(body, v) - } - } - - return response, err - } -} - -func (c *Client) backoff(retries int) { - time.Sleep(c.backoffFunc(retries)) -} - -func (c *Client) all(f func(int) (*Response, error)) error { - var ( - page = 1 - ) - for { - resp, err := f(page) - if err != nil { - return err - } - if resp.Meta.Pagination == nil || resp.Meta.Pagination.NextPage == 0 { - return nil - } - page = resp.Meta.Pagination.NextPage - } +func (c *Client) Do(req *http.Request, v any) (*Response, error) { + return c.handler.Do(req, v) } func (c *Client) buildUserAgent() { @@ -342,43 +336,6 @@ func (c *Client) buildUserAgent() { } } -func dumpRequest(r *http.Request) ([]byte, error) { - // Duplicate the request, so we can redact the auth header - rDuplicate := r.Clone(context.Background()) - rDuplicate.Header.Set("Authorization", "REDACTED") - - // To get the request body we need to read it before the request was actually sent. - // See https://github.com/golang/go/issues/29792 - dumpReq, err := httputil.DumpRequestOut(rDuplicate, true) - if err != nil { - return nil, err - } - - // Set original request body to the duplicate created by DumpRequestOut. The request body is not duplicated - // by .Clone() and instead just referenced, so it would be completely read otherwise. - r.Body = rDuplicate.Body - - return dumpReq, nil -} - -func errorFromResponse(resp *Response, body []byte) error { - if !strings.HasPrefix(resp.Header.Get("Content-Type"), "application/json") { - return nil - } - - var respBody schema.ErrorResponse - if err := json.Unmarshal(body, &respBody); err != nil { - return nil - } - if respBody.Error.Code == "" && respBody.Error.Message == "" { - return nil - } - - hcErr := ErrorFromSchema(respBody.Error) - hcErr.response = resp - return hcErr -} - const ( headerCorrelationID = "X-Correlation-Id" ) @@ -387,35 +344,34 @@ const ( type Response struct { *http.Response Meta Meta + + // body holds a copy of the http.Response body that must be used within the handler + // chain. The http.Response.Body is reserved for external users. + body []byte } -func (r *Response) readMeta(body []byte) error { - if h := r.Header.Get("RateLimit-Limit"); h != "" { - r.Meta.Ratelimit.Limit, _ = strconv.Atoi(h) - } - if h := r.Header.Get("RateLimit-Remaining"); h != "" { - r.Meta.Ratelimit.Remaining, _ = strconv.Atoi(h) - } - if h := r.Header.Get("RateLimit-Reset"); h != "" { - if ts, err := strconv.ParseInt(h, 10, 64); err == nil { - r.Meta.Ratelimit.Reset = time.Unix(ts, 0) - } +// populateBody copies the original [http.Response] body into the internal [Response] body +// property, and restore the original [http.Response] body as if it was untouched. +func (r *Response) populateBody() error { + // Read full response body and save it for later use + body, err := io.ReadAll(r.Body) + r.Body.Close() + if err != nil { + return err } + r.body = body - if strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") { - var s schema.MetaResponse - if err := json.Unmarshal(body, &s); err != nil { - return err - } - if s.Meta.Pagination != nil { - p := PaginationFromSchema(*s.Meta.Pagination) - r.Meta.Pagination = &p - } - } + // Restore the body as if it was untouched, as it might be read by external users + r.Body = io.NopCloser(bytes.NewReader(body)) return nil } +// hasJSONBody returns whether the response has a JSON body. +func (r *Response) hasJSONBody() bool { + return len(r.body) > 0 && strings.HasPrefix(r.Header.Get("Content-Type"), "application/json") +} + // internalCorrelationID returns the unique ID of the request as set by the API. This ID can help with support requests, // as it allows the people working on identify this request in particular. func (r *Response) internalCorrelationID() string { diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_generic.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_generic.go new file mode 100644 index 0000000000000000000000000000000000000000..dea12ad6358649d098d5ec2c2e33da7eb4d64185 --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_generic.go @@ -0,0 +1,101 @@ +package hcloud + +import ( + "bytes" + "context" + "encoding/json" + "io" +) + +func getRequest[Schema any](ctx context.Context, client *Client, url string) (Schema, *Response, error) { + var respBody Schema + + req, err := client.NewRequest(ctx, "GET", url, nil) + if err != nil { + return respBody, nil, err + } + + resp, err := client.Do(req, &respBody) + if err != nil { + return respBody, resp, err + } + + return respBody, resp, nil +} + +func postRequest[Schema any](ctx context.Context, client *Client, url string, reqBody any) (Schema, *Response, error) { + var respBody Schema + + var reqBodyReader io.Reader + if reqBody != nil { + reqBodyBytes, err := json.Marshal(reqBody) + if err != nil { + return respBody, nil, err + } + + reqBodyReader = bytes.NewReader(reqBodyBytes) + } + + req, err := client.NewRequest(ctx, "POST", url, reqBodyReader) + if err != nil { + return respBody, nil, err + } + + resp, err := client.Do(req, &respBody) + if err != nil { + return respBody, resp, err + } + + return respBody, resp, nil +} + +func putRequest[Schema any](ctx context.Context, client *Client, url string, reqBody any) (Schema, *Response, error) { + var respBody Schema + + var reqBodyReader io.Reader + if reqBody != nil { + reqBodyBytes, err := json.Marshal(reqBody) + if err != nil { + return respBody, nil, err + } + + reqBodyReader = bytes.NewReader(reqBodyBytes) + } + + req, err := client.NewRequest(ctx, "PUT", url, reqBodyReader) + if err != nil { + return respBody, nil, err + } + + resp, err := client.Do(req, &respBody) + if err != nil { + return respBody, resp, err + } + + return respBody, resp, nil +} + +func deleteRequest[Schema any](ctx context.Context, client *Client, url string) (Schema, *Response, error) { + var respBody Schema + + req, err := client.NewRequest(ctx, "DELETE", url, nil) + if err != nil { + return respBody, nil, err + } + + resp, err := client.Do(req, &respBody) + if err != nil { + return respBody, resp, err + } + + return respBody, resp, nil +} + +func deleteRequestNoResult(ctx context.Context, client *Client, url string) (*Response, error) { + req, err := client.NewRequest(ctx, "DELETE", url, nil) + if err != nil { + return nil, err + } + + return client.Do(req, nil) +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler.go new file mode 100644 index 0000000000000000000000000000000000000000..29a9376d6bf9aa3e3f951ba81fcca24518e26dd5 --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler.go @@ -0,0 +1,56 @@ +package hcloud + +import ( + "context" + "net/http" +) + +// handler is an interface representing a client request transaction. The handler are +// meant to be chained, similarly to the [http.RoundTripper] interface. +// +// The handler chain is placed between the [Client] API operations and the +// [http.Client]. +type handler interface { + Do(req *http.Request, v any) (resp *Response, err error) +} + +// assembleHandlerChain assembles the chain of handlers used to make API requests. +// +// The order of the handlers is important. +func assembleHandlerChain(client *Client) handler { + // Start down the chain: sending the http request + h := newHTTPHandler(client.httpClient) + + // Insert debug writer if enabled + if client.debugWriter != nil { + h = wrapDebugHandler(h, client.debugWriter) + } + + // Read rate limit headers + h = wrapRateLimitHandler(h) + + // Build error from response + h = wrapErrorHandler(h) + + // Retry request if condition are met + h = wrapRetryHandler(h, client.retryBackoffFunc, client.retryMaxRetries) + + // Finally parse the response body into the provided schema + h = wrapParseHandler(h) + + return h +} + +// cloneRequest clones both the request and the request body. +func cloneRequest(req *http.Request, ctx context.Context) (cloned *http.Request, err error) { //revive:disable:context-as-argument + cloned = req.Clone(ctx) + + if req.ContentLength > 0 { + cloned.Body, err = req.GetBody() + if err != nil { + return nil, err + } + } + + return cloned, nil +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_debug.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_debug.go new file mode 100644 index 0000000000000000000000000000000000000000..4aa867db44ed28bd05a544dee3fbc93b2b1d772c --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_debug.go @@ -0,0 +1,50 @@ +package hcloud + +import ( + "context" + "fmt" + "io" + "net/http" + "net/http/httputil" +) + +func wrapDebugHandler(wrapped handler, output io.Writer) handler { + return &debugHandler{wrapped, output} +} + +type debugHandler struct { + handler handler + output io.Writer +} + +func (h *debugHandler) Do(req *http.Request, v any) (resp *Response, err error) { + // Clone the request, so we can redact the auth header, read the body + // and use a new context. + cloned, err := cloneRequest(req, context.Background()) + if err != nil { + return nil, err + } + + cloned.Header.Set("Authorization", "REDACTED") + + dumpReq, err := httputil.DumpRequestOut(cloned, true) + if err != nil { + return nil, err + } + + fmt.Fprintf(h.output, "--- Request:\n%s\n\n", dumpReq) + + resp, err = h.handler.Do(req, v) + if err != nil { + return resp, err + } + + dumpResp, err := httputil.DumpResponse(resp.Response, true) + if err != nil { + return nil, err + } + + fmt.Fprintf(h.output, "--- Response:\n%s\n\n", dumpResp) + + return resp, err +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_error.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_error.go new file mode 100644 index 0000000000000000000000000000000000000000..30c893fa9384fec2379eb9639502dcdbd0e151d0 --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_error.go @@ -0,0 +1,53 @@ +package hcloud + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" +) + +var ErrStatusCode = errors.New("server responded with status code") + +func wrapErrorHandler(wrapped handler) handler { + return &errorHandler{wrapped} +} + +type errorHandler struct { + handler handler +} + +func (h *errorHandler) Do(req *http.Request, v any) (resp *Response, err error) { + resp, err = h.handler.Do(req, v) + if err != nil { + return resp, err + } + + if resp.StatusCode >= 400 && resp.StatusCode <= 599 { + err = errorFromBody(resp) + if err == nil { + err = fmt.Errorf("hcloud: %w %d", ErrStatusCode, resp.StatusCode) + } + } + return resp, err +} + +func errorFromBody(resp *Response) error { + if !resp.hasJSONBody() { + return nil + } + + var s schema.ErrorResponse + if err := json.Unmarshal(resp.body, &s); err != nil { + return nil // nolint: nilerr + } + if s.Error.Code == "" && s.Error.Message == "" { + return nil + } + + hcErr := ErrorFromSchema(s.Error) + hcErr.response = resp + return hcErr +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_http.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_http.go new file mode 100644 index 0000000000000000000000000000000000000000..c0a02fe3986818fa0ad485028e98969718cd93ef --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_http.go @@ -0,0 +1,28 @@ +package hcloud + +import ( + "net/http" +) + +func newHTTPHandler(httpClient *http.Client) handler { + return &httpHandler{httpClient} +} + +type httpHandler struct { + httpClient *http.Client +} + +func (h *httpHandler) Do(req *http.Request, _ interface{}) (*Response, error) { + httpResponse, err := h.httpClient.Do(req) //nolint: bodyclose + resp := &Response{Response: httpResponse} + if err != nil { + return resp, err + } + + err = resp.populateBody() + if err != nil { + return resp, err + } + + return resp, err +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_parse.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_parse.go new file mode 100644 index 0000000000000000000000000000000000000000..65e075b219dbcd84447f763c4d9e8d73bb4d39c9 --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_parse.go @@ -0,0 +1,50 @@ +package hcloud + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" +) + +func wrapParseHandler(wrapped handler) handler { + return &parseHandler{wrapped} +} + +type parseHandler struct { + handler handler +} + +func (h *parseHandler) Do(req *http.Request, v any) (resp *Response, err error) { + // respBody is not needed down the handler chain + resp, err = h.handler.Do(req, nil) + if err != nil { + return resp, err + } + + if resp.hasJSONBody() { + // Parse the response meta + var s schema.MetaResponse + if err := json.Unmarshal(resp.body, &s); err != nil { + return resp, fmt.Errorf("hcloud: error reading response meta data: %w", err) + } + if s.Meta.Pagination != nil { + p := PaginationFromSchema(*s.Meta.Pagination) + resp.Meta.Pagination = &p + } + } + + // Parse the response schema + if v != nil { + if w, ok := v.(io.Writer); ok { + _, err = io.Copy(w, bytes.NewReader(resp.body)) + } else { + err = json.Unmarshal(resp.body, v) + } + } + + return resp, err +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_rate_limit.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_rate_limit.go new file mode 100644 index 0000000000000000000000000000000000000000..4c53ba9e9a13fcf00e9e2daadba568c5e97c6bda --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_rate_limit.go @@ -0,0 +1,36 @@ +package hcloud + +import ( + "net/http" + "strconv" + "time" +) + +func wrapRateLimitHandler(wrapped handler) handler { + return &rateLimitHandler{wrapped} +} + +type rateLimitHandler struct { + handler handler +} + +func (h *rateLimitHandler) Do(req *http.Request, v any) (resp *Response, err error) { + resp, err = h.handler.Do(req, v) + + // Ensure the embedded [*http.Response] is not nil, e.g. on canceled context + if resp != nil && resp.Response != nil && resp.Response.Header != nil { + if h := resp.Header.Get("RateLimit-Limit"); h != "" { + resp.Meta.Ratelimit.Limit, _ = strconv.Atoi(h) + } + if h := resp.Header.Get("RateLimit-Remaining"); h != "" { + resp.Meta.Ratelimit.Remaining, _ = strconv.Atoi(h) + } + if h := resp.Header.Get("RateLimit-Reset"); h != "" { + if ts, err := strconv.ParseInt(h, 10, 64); err == nil { + resp.Meta.Ratelimit.Reset = time.Unix(ts, 0) + } + } + } + + return resp, err +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_retry.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_retry.go new file mode 100644 index 0000000000000000000000000000000000000000..b0983c9699486c29e4d51bde699be071770bc9aa --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_handler_retry.go @@ -0,0 +1,84 @@ +package hcloud + +import ( + "errors" + "net" + "net/http" + "time" +) + +func wrapRetryHandler(wrapped handler, backoffFunc BackoffFunc, maxRetries int) handler { + return &retryHandler{wrapped, backoffFunc, maxRetries} +} + +type retryHandler struct { + handler handler + backoffFunc BackoffFunc + maxRetries int +} + +func (h *retryHandler) Do(req *http.Request, v any) (resp *Response, err error) { + retries := 0 + ctx := req.Context() + + for { + // Clone the request using the original context + cloned, err := cloneRequest(req, ctx) + if err != nil { + return nil, err + } + + resp, err = h.handler.Do(cloned, v) + if err != nil { + // Beware the diversity of the errors: + // - request preparation + // - network connectivity + // - http status code (see [errorHandler]) + if ctx.Err() != nil { + // early return if the context was canceled or timed out + return resp, err + } + + if retries < h.maxRetries && retryPolicy(resp, err) { + select { + case <-ctx.Done(): + return resp, err + case <-time.After(h.backoffFunc(retries)): + retries++ + continue + } + } + } + + return resp, err + } +} + +func retryPolicy(resp *Response, err error) bool { + if err != nil { + var apiErr Error + var netErr net.Error + + switch { + case errors.As(err, &apiErr): + switch apiErr.Code { //nolint:exhaustive + case ErrorCodeConflict: + return true + case ErrorCodeRateLimitExceeded: + return true + } + case errors.Is(err, ErrStatusCode): + switch resp.Response.StatusCode { + // 5xx errors + case http.StatusBadGateway, http.StatusGatewayTimeout: + return true + } + case errors.As(err, &netErr): + if netErr.Timeout() { + return true + } + } + } + + return false +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_helper.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_helper.go new file mode 100644 index 0000000000000000000000000000000000000000..819ae2460ebd8038f6e02a9ab7415a3c260c7b1b --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client_helper.go @@ -0,0 +1,85 @@ +package hcloud + +import ( + "context" + "strconv" +) + +// allFromSchemaFunc transform each item in the list using the FromSchema function, and +// returns the result. +func allFromSchemaFunc[T, V any](all []T, fn func(T) V) []V { + result := make([]V, len(all)) + for i, t := range all { + result[i] = fn(t) + } + + return result +} + +// iterPages fetches each pages using the list function, and returns the result. +func iterPages[T any](listFn func(int) ([]*T, *Response, error)) ([]*T, error) { + page := 1 + result := []*T{} + + for { + pageResult, resp, err := listFn(page) + if err != nil { + return nil, err + } + + result = append(result, pageResult...) + + if resp.Meta.Pagination == nil || resp.Meta.Pagination.NextPage == 0 { + return result, nil + } + page = resp.Meta.Pagination.NextPage + } +} + +// firstBy fetches a list of items using the list function, and returns the first item +// of the list if present otherwise nil. +func firstBy[T any](listFn func() ([]*T, *Response, error)) (*T, *Response, error) { + items, resp, err := listFn() + if len(items) == 0 { + return nil, resp, err + } + + return items[0], resp, err +} + +// firstByName is a wrapper around [firstBy], that checks if the provided name is not +// empty. +func firstByName[T any](name string, listFn func() ([]*T, *Response, error)) (*T, *Response, error) { + if name == "" { + return nil, nil, nil + } + + return firstBy(listFn) +} + +// getByIDOrName fetches the resource by ID when the identifier is an integer, otherwise +// by Name. To support resources that have a integer as Name, an additional attempt is +// made to fetch the resource by Name using the ID. +// +// Since API managed resources (locations, server types, ...) do not have integers as +// names, this function is only meaningful for user managed resources (ssh keys, +// servers). +func getByIDOrName[T any]( + ctx context.Context, + getByIDFn func(ctx context.Context, id int64) (*T, *Response, error), + getByNameFn func(ctx context.Context, name string) (*T, *Response, error), + idOrName string, +) (*T, *Response, error) { + if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { + result, resp, err := getByIDFn(ctx, id) + if err != nil { + return result, resp, err + } + if result != nil { + return result, resp, err + } + // Fallback to get by Name if the resource was not found + } + + return getByNameFn(ctx, idOrName) +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/datacenter.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/datacenter.go index f7e9283ed6d7153e340781605205ecc34571d4c4..f821c90eb9e8bdcc3d4661f43edfeda4a23b3cad 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/datacenter.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/datacenter.go @@ -6,6 +6,7 @@ import ( "net/url" "strconv" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -32,32 +33,27 @@ type DatacenterClient struct { // GetByID retrieves a datacenter by its ID. If the datacenter does not exist, nil is returned. func (c *DatacenterClient) GetByID(ctx context.Context, id int64) (*Datacenter, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/datacenters/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/datacenters/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.DatacenterGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.DatacenterGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } return nil, resp, err } - return DatacenterFromSchema(body.Datacenter), resp, nil + + return DatacenterFromSchema(respBody.Datacenter), resp, nil } // GetByName retrieves a datacenter by its name. If the datacenter does not exist, nil is returned. func (c *DatacenterClient) GetByName(ctx context.Context, name string) (*Datacenter, *Response, error) { - if name == "" { - return nil, nil, nil - } - datacenters, response, err := c.List(ctx, DatacenterListOpts{Name: name}) - if len(datacenters) == 0 { - return nil, response, err - } - return datacenters[0], response, err + return firstByName(name, func() ([]*Datacenter, *Response, error) { + return c.List(ctx, DatacenterListOpts{Name: name}) + }) } // Get retrieves a datacenter by its ID if the input can be parsed as an integer, otherwise it @@ -92,22 +88,17 @@ func (l DatacenterListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *DatacenterClient) List(ctx context.Context, opts DatacenterListOpts) ([]*Datacenter, *Response, error) { - path := "/datacenters?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/datacenters?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) - var body schema.DatacenterListResponse - resp, err := c.client.Do(req, &body) + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) + + respBody, resp, err := getRequest[schema.DatacenterListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - datacenters := make([]*Datacenter, 0, len(body.Datacenters)) - for _, i := range body.Datacenters { - datacenters = append(datacenters, DatacenterFromSchema(i)) + return nil, resp, err } - return datacenters, resp, nil + + return allFromSchemaFunc(respBody.Datacenters, DatacenterFromSchema), resp, nil } // All returns all datacenters. @@ -117,20 +108,8 @@ func (c *DatacenterClient) All(ctx context.Context) ([]*Datacenter, error) { // AllWithOpts returns all datacenters for the given options. func (c *DatacenterClient) AllWithOpts(ctx context.Context, opts DatacenterListOpts) ([]*Datacenter, error) { - allDatacenters := []*Datacenter{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*Datacenter, *Response, error) { opts.Page = page - datacenters, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allDatacenters = append(allDatacenters, datacenters...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allDatacenters, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/error.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/error.go index 371a92e31edbeed9832ac5cdab95bf914907747d..0e6f556b9bb9fa1a5f06140c1384135350a6d7d8 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/error.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/error.go @@ -4,6 +4,8 @@ import ( "errors" "fmt" "net" + "slices" + "strings" ) // ErrorCode represents an error code returned from the API. @@ -29,6 +31,7 @@ const ( ErrorCodeRobotUnavailable ErrorCode = "robot_unavailable" // Robot was not available. The caller may retry the operation after a short delay ErrorCodeResourceLocked ErrorCode = "resource_locked" // The resource is locked. The caller should contact support ErrorUnsupportedError ErrorCode = "unsupported_error" // The given resource does not support this + ErrorDeprecatedAPIEndpoint ErrorCode = "deprecated_api_endpoint" // The request can not be answered because the API functionality was removed // Server related error codes. @@ -126,11 +129,16 @@ type ErrorDetailsInvalidInputField struct { Messages []string } -// IsError returns whether err is an API error with the given error code. -func IsError(err error, code ErrorCode) bool { +// ErrorDetailsDeprecatedAPIEndpoint contains the details of a 'deprecated_api_endpoint' error. +type ErrorDetailsDeprecatedAPIEndpoint struct { + Announcement string +} + +// IsError returns whether err is an API error with one of the given error codes. +func IsError(err error, code ...ErrorCode) bool { var apiErr Error ok := errors.As(err, &apiErr) - return ok && apiErr.Code == code + return ok && slices.Index(code, apiErr.Code) > -1 } type InvalidIPError struct { @@ -148,3 +156,40 @@ type DNSNotFoundError struct { func (e DNSNotFoundError) Error() string { return fmt.Sprintf("dns for ip %s not found", e.IP.String()) } + +// ArgumentError is a type of error returned when validating arguments. +type ArgumentError string + +func (e ArgumentError) Error() string { return string(e) } + +func newArgumentErrorf(format string, args ...any) ArgumentError { + return ArgumentError(fmt.Sprintf(format, args...)) +} + +func missingArgument(name string, obj any) error { + return newArgumentErrorf("missing argument '%s' [%T]", name, obj) +} + +func invalidArgument(name string, obj any) error { + return newArgumentErrorf("invalid value '%v' for argument '%s' [%T]", obj, name, obj) +} + +func missingField(obj any, field string) error { + return newArgumentErrorf("missing field [%s] in [%T]", field, obj) +} + +func invalidFieldValue(obj any, field string, value any) error { + return newArgumentErrorf("invalid value '%v' for field [%s] in [%T]", value, field, obj) +} + +func missingOneOfFields(obj any, fields ...string) error { + return newArgumentErrorf("missing one of fields [%s] in [%T]", strings.Join(fields, ", "), obj) +} + +func mutuallyExclusiveFields(obj any, fields ...string) error { + return newArgumentErrorf("found mutually exclusive fields [%s] in [%T]", strings.Join(fields, ", "), obj) +} + +func missingRequiredTogetherFields(obj any, fields ...string) error { + return newArgumentErrorf("missing required together fields [%s] in [%T]", strings.Join(fields, ", "), obj) +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/actionutil/actions.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/actionutil/actions.go new file mode 100644 index 0000000000000000000000000000000000000000..b645d2bb0233922a0f46e937f0b53bfc51991083 --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/actionutil/actions.go @@ -0,0 +1,11 @@ +package actionutil + +import "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud" + +// AppendNext return the action and the next actions in a new slice. +func AppendNext(action *hcloud.Action, nextActions []*hcloud.Action) []*hcloud.Action { + all := make([]*hcloud.Action, 0, 1+len(nextActions)) + all = append(all, action) + all = append(all, nextActions...) + return all +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil/ctxutil.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil/ctxutil.go new file mode 100644 index 0000000000000000000000000000000000000000..de0ce95f5cce05ca5484ef2d351092f88b9fe308 --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil/ctxutil.go @@ -0,0 +1,30 @@ +package ctxutil + +import ( + "context" + "strings" +) + +// key is an unexported type to prevents collisions with keys defined in other packages. +type key struct{} + +// opPathKey is the key for operation path in Contexts. +var opPathKey = key{} + +// SetOpPath processes the operation path and save it in the context before returning it. +func SetOpPath(ctx context.Context, path string) context.Context { + path, _, _ = strings.Cut(path, "?") + path = strings.ReplaceAll(path, "%d", "-") + path = strings.ReplaceAll(path, "%s", "-") + + return context.WithValue(ctx, opPathKey, path) +} + +// OpPath returns the operation path from the context. +func OpPath(ctx context.Context) string { + result, ok := ctx.Value(opPathKey).(string) + if !ok { + return "" + } + return result +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/doc.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/doc.go new file mode 100644 index 0000000000000000000000000000000000000000..96506ae2557f2fbf3a1e552da986d6afa36c7c89 --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/doc.go @@ -0,0 +1,4 @@ +// Package exp is a namespace that holds experimental features for the `hcloud-go` library. +// +// Breaking changes may occur without notice. Do not use in production! +package exp diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/envutil/env.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/envutil/env.go new file mode 100644 index 0000000000000000000000000000000000000000..7e6aa6ee6ff9bae11f9b9600ddcbd6bf31880f56 --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/envutil/env.go @@ -0,0 +1,40 @@ +package envutil + +import ( + "fmt" + "os" + "strings" +) + +// LookupEnvWithFile retrieves the value of the environment variable named by the key (e.g. +// HCLOUD_TOKEN). If the previous environment variable is not set, it retrieves the +// content of the file located by a second environment variable named by the key + +// '_FILE' (.e.g HCLOUD_TOKEN_FILE). +// +// For both cases, the returned value may be empty. +// +// The value from the environment takes precedence over the value from the file. +func LookupEnvWithFile(key string) (string, error) { + // Check if the value is set in the environment (e.g. HCLOUD_TOKEN) + value, ok := os.LookupEnv(key) + if ok { + return value, nil + } + + key += "_FILE" + + // Check if the value is set via a file (e.g. HCLOUD_TOKEN_FILE) + valueFile, ok := os.LookupEnv(key) + if !ok { + // Validation of the value happens outside of this function + return "", nil + } + + // Read the content of the file + valueBytes, err := os.ReadFile(valueFile) + if err != nil { + return "", fmt.Errorf("failed to read %s: %w", key, err) + } + + return strings.TrimSpace(string(valueBytes)), nil +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/randutil/id.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/randutil/id.go new file mode 100644 index 0000000000000000000000000000000000000000..75dab5f400c132309854b28292e55601f3cb969d --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/randutil/id.go @@ -0,0 +1,19 @@ +package randutil + +import ( + "crypto/rand" + "encoding/hex" + "fmt" +) + +// GenerateID returns a hex encoded random string with a len of 8 chars similar to +// "2873fce7". +func GenerateID() string { + b := make([]byte, 4) + _, err := rand.Read(b) + if err != nil { + // Should never happen as of go1.24: https://github.com/golang/go/issues/66821 + panic(fmt.Errorf("failed to generate random string: %w", err)) + } + return hex.EncodeToString(b) +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/sshutil/ssh_key.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/sshutil/ssh_key.go new file mode 100644 index 0000000000000000000000000000000000000000..4465d5c70fbed9d419a18ca7ed6a5dd4a622e98a --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/kit/sshutil/ssh_key.go @@ -0,0 +1,86 @@ +package sshutil + +import ( + "crypto" + "crypto/ed25519" + "encoding/pem" + "fmt" + + "golang.org/x/crypto/ssh" +) + +// GenerateKeyPair generates a new ed25519 ssh key pair, and returns the private key and +// the public key respectively. +func GenerateKeyPair() ([]byte, []byte, error) { + pub, priv, err := ed25519.GenerateKey(nil) + if err != nil { + return nil, nil, fmt.Errorf("could not generate key pair: %w", err) + } + + privBytes, err := encodePrivateKey(priv) + if err != nil { + return nil, nil, fmt.Errorf("could not encode private key: %w", err) + } + + pubBytes, err := encodePublicKey(pub) + if err != nil { + return nil, nil, fmt.Errorf("could not encode public key: %w", err) + } + + return privBytes, pubBytes, nil +} + +func encodePrivateKey(priv crypto.PrivateKey) ([]byte, error) { + privPem, err := ssh.MarshalPrivateKey(priv, "") + if err != nil { + return nil, err + } + + return pem.EncodeToMemory(privPem), nil +} + +func encodePublicKey(pub crypto.PublicKey) ([]byte, error) { + sshPub, err := ssh.NewPublicKey(pub) + if err != nil { + return nil, err + } + + return ssh.MarshalAuthorizedKey(sshPub), nil +} + +type privateKeyWithPublicKey interface { + crypto.PrivateKey + Public() crypto.PublicKey +} + +// GeneratePublicKey generate a public key from the provided private key. +func GeneratePublicKey(privBytes []byte) ([]byte, error) { + priv, err := ssh.ParseRawPrivateKey(privBytes) + if err != nil { + return nil, fmt.Errorf("could not decode private key: %w", err) + } + + key, ok := priv.(privateKeyWithPublicKey) + if !ok { + return nil, fmt.Errorf("private key doesn't export Public() crypto.PublicKey") + } + + pubBytes, err := encodePublicKey(key.Public()) + if err != nil { + return nil, fmt.Errorf("could not encode public key: %w", err) + } + + return pubBytes, nil +} + +// GetPublicKeyFingerprint generate the finger print for the provided public key. +func GetPublicKeyFingerprint(pubBytes []byte) (string, error) { + pub, _, _, _, err := ssh.ParseAuthorizedKey(pubBytes) + if err != nil { + return "", fmt.Errorf("could not decode public key: %w", err) + } + + fingerprint := ssh.FingerprintLegacyMD5(pub) + + return fingerprint, nil +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/labelutil/selector.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/labelutil/selector.go new file mode 100644 index 0000000000000000000000000000000000000000..c5e84f08a2e547a83b03b6df14f43cb8b5498ce9 --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/labelutil/selector.go @@ -0,0 +1,24 @@ +package labelutil + +import ( + "fmt" + "sort" + "strings" +) + +// Selector combines the label set into a [label selector](https://docs.hetzner.cloud/#label-selector) that only selects +// resources have all specified labels set. +// +// The selector string can be used to filter resources when listing, for example with [hcloud.ServerClient.AllWithOpts()]. +func Selector(labels map[string]string) string { + selectors := make([]string, 0, len(labels)) + + for k, v := range labels { + selectors = append(selectors, fmt.Sprintf("%s=%s", k, v)) + } + + // Reproducible result for tests + sort.Strings(selectors) + + return strings.Join(selectors, ",") +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/mockutil/http.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/mockutil/http.go new file mode 100644 index 0000000000000000000000000000000000000000..26ff4874022758fab75672c2698a8536f23629ee --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/mockutil/http.go @@ -0,0 +1,123 @@ +package mockutil + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Request describes a http request that a [httptest.Server] should receive, and the +// corresponding response to return. +// +// Additional checks on the request (e.g. request body) may be added using the +// [Request.Want] function. +// +// The response body is populated from either a JSON struct, or a JSON string. +type Request struct { + Method string + Path string + Want func(t *testing.T, r *http.Request) + + Status int + JSON any + JSONRaw string +} + +// Handler is using a [Server] to mock http requests provided by the user. +func Handler(t *testing.T, requests []Request) http.HandlerFunc { + t.Helper() + + server := NewServer(t, requests) + t.Cleanup(server.close) + + return server.handler +} + +// NewServer returns a new mock server that closes itself at the end of the test. +func NewServer(t *testing.T, requests []Request) *Server { + t.Helper() + + o := &Server{t: t} + o.Server = httptest.NewServer(http.HandlerFunc(o.handler)) + t.Cleanup(o.close) + + o.Expect(requests) + + return o +} + +// Server embeds a [httptest.Server] that answers HTTP calls with a list of expected [Request]. +// +// Request matching is based on the request count, and the user provided request will be +// iterated over. +// +// A Server must be created using the [NewServer] function. +type Server struct { + *httptest.Server + + t *testing.T + + requests []Request + index int +} + +// Expect adds requests to the list of requests expected by the [Server]. +func (m *Server) Expect(requests []Request) { + m.requests = append(m.requests, requests...) +} + +func (m *Server) close() { + m.t.Helper() + + m.Server.Close() + + assert.EqualValues(m.t, len(m.requests), m.index, "expected more calls") +} + +func (m *Server) handler(w http.ResponseWriter, r *http.Request) { + if testing.Verbose() { + m.t.Logf("call %d: %s %s\n", m.index, r.Method, r.RequestURI) + } + + if m.index >= len(m.requests) { + m.t.Fatalf("received unknown call %d", m.index) + } + + expected := m.requests[m.index] + + expectedCall := expected.Method + foundCall := r.Method + if expected.Path != "" { + expectedCall += " " + expected.Path + foundCall += " " + r.RequestURI + } + require.Equal(m.t, expectedCall, foundCall) // nolint: testifylint + + if expected.Want != nil { + expected.Want(m.t, r) + } + + switch { + case expected.JSON != nil: + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(expected.Status) + if err := json.NewEncoder(w).Encode(expected.JSON); err != nil { + m.t.Fatal(err) + } + case expected.JSONRaw != "": + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(expected.Status) + _, err := w.Write([]byte(expected.JSONRaw)) + if err != nil { + m.t.Fatal(err) + } + default: + w.WriteHeader(expected.Status) + } + + m.index++ +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/firewall.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/firewall.go index f046e7344f91f0d52272c787093f49c3c14192ab..11b28064cd26d999f045956516c6834ef9762f33 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/firewall.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/firewall.go @@ -1,16 +1,13 @@ package hcloud import ( - "bytes" "context" - "encoding/json" - "errors" "fmt" "net" "net/url" - "strconv" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -96,41 +93,33 @@ type FirewallClient struct { // GetByID retrieves a Firewall by its ID. If the Firewall does not exist, nil is returned. func (c *FirewallClient) GetByID(ctx context.Context, id int64) (*Firewall, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/firewalls/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/firewalls/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.FirewallGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.FirewallGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return FirewallFromSchema(body.Firewall), resp, nil + + return FirewallFromSchema(respBody.Firewall), resp, nil } // GetByName retrieves a Firewall by its name. If the Firewall does not exist, nil is returned. func (c *FirewallClient) GetByName(ctx context.Context, name string) (*Firewall, *Response, error) { - if name == "" { - return nil, nil, nil - } - firewalls, response, err := c.List(ctx, FirewallListOpts{Name: name}) - if len(firewalls) == 0 { - return nil, response, err - } - return firewalls[0], response, err + return firstByName(name, func() ([]*Firewall, *Response, error) { + return c.List(ctx, FirewallListOpts{Name: name}) + }) } // Get retrieves a Firewall by its ID if the input can be parsed as an integer, otherwise it // retrieves a Firewall by its name. If the Firewall does not exist, nil is returned. func (c *FirewallClient) Get(ctx context.Context, idOrName string) (*Firewall, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByName(ctx, idOrName) + return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName) } // FirewallListOpts specifies options for listing Firewalls. @@ -156,22 +145,17 @@ func (l FirewallListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *FirewallClient) List(ctx context.Context, opts FirewallListOpts) ([]*Firewall, *Response, error) { - path := "/firewalls?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/firewalls?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) - var body schema.FirewallListResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.FirewallListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - firewalls := make([]*Firewall, 0, len(body.Firewalls)) - for _, s := range body.Firewalls { - firewalls = append(firewalls, FirewallFromSchema(s)) + return nil, resp, err } - return firewalls, resp, nil + + return allFromSchemaFunc(respBody.Firewalls, FirewallFromSchema), resp, nil } // All returns all Firewalls. @@ -181,22 +165,10 @@ func (c *FirewallClient) All(ctx context.Context) ([]*Firewall, error) { // AllWithOpts returns all Firewalls for the given options. func (c *FirewallClient) AllWithOpts(ctx context.Context, opts FirewallListOpts) ([]*Firewall, error) { - allFirewalls := []*Firewall{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*Firewall, *Response, error) { opts.Page = page - firewalls, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allFirewalls = append(allFirewalls, firewalls...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allFirewalls, nil } // FirewallCreateOpts specifies options for creating a new Firewall. @@ -210,7 +182,7 @@ type FirewallCreateOpts struct { // Validate checks if options are valid. func (o FirewallCreateOpts) Validate() error { if o.Name == "" { - return errors.New("missing name") + return missingField(o, "Name") } return nil } @@ -223,28 +195,27 @@ type FirewallCreateResult struct { // Create creates a new Firewall. func (c *FirewallClient) Create(ctx context.Context, opts FirewallCreateOpts) (FirewallCreateResult, *Response, error) { + const opPath = "/firewalls" + ctx = ctxutil.SetOpPath(ctx, opPath) + + result := FirewallCreateResult{} + + reqPath := opPath + if err := opts.Validate(); err != nil { - return FirewallCreateResult{}, nil, err + return result, nil, err } + reqBody := firewallCreateOptsToSchema(opts) - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return FirewallCreateResult{}, nil, err - } - req, err := c.client.NewRequest(ctx, "POST", "/firewalls", bytes.NewReader(reqBodyData)) - if err != nil { - return FirewallCreateResult{}, nil, err - } - respBody := schema.FirewallCreateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.FirewallCreateResponse](ctx, c.client, reqPath, reqBody) if err != nil { - return FirewallCreateResult{}, resp, err - } - result := FirewallCreateResult{ - Firewall: FirewallFromSchema(respBody.Firewall), - Actions: ActionsFromSchema(respBody.Actions), + return result, resp, err } + + result.Firewall = FirewallFromSchema(respBody.Firewall) + result.Actions = ActionsFromSchema(respBody.Actions) + return result, resp, nil } @@ -256,6 +227,11 @@ type FirewallUpdateOpts struct { // Update updates a Firewall. func (c *FirewallClient) Update(ctx context.Context, firewall *Firewall, opts FirewallUpdateOpts) (*Firewall, *Response, error) { + const opPath = "/firewalls/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, firewall.ID) + reqBody := schema.FirewallUpdateRequest{} if opts.Name != "" { reqBody.Name = &opts.Name @@ -263,32 +239,23 @@ func (c *FirewallClient) Update(ctx context.Context, firewall *Firewall, opts Fi if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/firewalls/%d", firewall.ID) - req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.FirewallUpdateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := putRequest[schema.FirewallUpdateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return FirewallFromSchema(respBody.Firewall), resp, nil } // Delete deletes a Firewall. func (c *FirewallClient) Delete(ctx context.Context, firewall *Firewall) (*Response, error) { - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/firewalls/%d", firewall.ID), nil) - if err != nil { - return nil, err - } - return c.client.Do(req, nil) + const opPath = "/firewalls/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, firewall.ID) + + return deleteRequestNoResult(ctx, c.client, reqPath) } // FirewallSetRulesOpts specifies options for setting rules of a Firewall. @@ -298,75 +265,59 @@ type FirewallSetRulesOpts struct { // SetRules sets the rules of a Firewall. func (c *FirewallClient) SetRules(ctx context.Context, firewall *Firewall, opts FirewallSetRulesOpts) ([]*Action, *Response, error) { - reqBody := firewallSetRulesOptsToSchema(opts) + const opPath = "/firewalls/%d/actions/set_rules" + ctx = ctxutil.SetOpPath(ctx, opPath) - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } + reqPath := fmt.Sprintf(opPath, firewall.ID) - path := fmt.Sprintf("/firewalls/%d/actions/set_rules", firewall.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } + reqBody := firewallSetRulesOptsToSchema(opts) - var respBody schema.FirewallActionSetRulesResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.FirewallActionSetRulesResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionsFromSchema(respBody.Actions), resp, nil } func (c *FirewallClient) ApplyResources(ctx context.Context, firewall *Firewall, resources []FirewallResource) ([]*Action, *Response, error) { + const opPath = "/firewalls/%d/actions/apply_to_resources" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, firewall.ID) + applyTo := make([]schema.FirewallResource, len(resources)) for i, r := range resources { applyTo[i] = firewallResourceToSchema(r) } reqBody := schema.FirewallActionApplyToResourcesRequest{ApplyTo: applyTo} - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/firewalls/%d/actions/apply_to_resources", firewall.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - var respBody schema.FirewallActionApplyToResourcesResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.FirewallActionApplyToResourcesResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionsFromSchema(respBody.Actions), resp, nil } func (c *FirewallClient) RemoveResources(ctx context.Context, firewall *Firewall, resources []FirewallResource) ([]*Action, *Response, error) { + const opPath = "/firewalls/%d/actions/remove_from_resources" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, firewall.ID) + removeFrom := make([]schema.FirewallResource, len(resources)) for i, r := range resources { removeFrom[i] = firewallResourceToSchema(r) } reqBody := schema.FirewallActionRemoveFromResourcesRequest{RemoveFrom: removeFrom} - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/firewalls/%d/actions/remove_from_resources", firewall.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - var respBody schema.FirewallActionRemoveFromResourcesResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.FirewallActionRemoveFromResourcesResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionsFromSchema(respBody.Actions), resp, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/floating_ip.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/floating_ip.go index 569576c0209fa3051ca76219b75688a16ad100a4..e200b48dca3f4513f2b1cfc98f2d718ee051321e 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/floating_ip.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/floating_ip.go @@ -1,16 +1,13 @@ package hcloud import ( - "bytes" "context" - "encoding/json" - "errors" "fmt" "net" "net/url" - "strconv" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -54,26 +51,21 @@ const ( // changeDNSPtr changes or resets the reverse DNS pointer for an IP address. // Pass a nil ptr to reset the reverse DNS pointer to its default value. func (f *FloatingIP) changeDNSPtr(ctx context.Context, client *Client, ip net.IP, ptr *string) (*Action, *Response, error) { + const opPath = "/floating_ips/%d/actions/change_dns_ptr" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, f.ID) + reqBody := schema.FloatingIPActionChangeDNSPtrRequest{ IP: ip.String(), DNSPtr: ptr, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/floating_ips/%d/actions/change_dns_ptr", f.ID) - req, err := client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - respBody := schema.FloatingIPActionChangeDNSPtrResponse{} - resp, err := client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.FloatingIPActionChangeDNSPtrResponse](ctx, client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -97,41 +89,33 @@ type FloatingIPClient struct { // GetByID retrieves a Floating IP by its ID. If the Floating IP does not exist, // nil is returned. func (c *FloatingIPClient) GetByID(ctx context.Context, id int64) (*FloatingIP, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/floating_ips/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/floating_ips/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.FloatingIPGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.FloatingIPGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } return nil, resp, err } - return FloatingIPFromSchema(body.FloatingIP), resp, nil + + return FloatingIPFromSchema(respBody.FloatingIP), resp, nil } // GetByName retrieves a Floating IP by its name. If the Floating IP does not exist, nil is returned. func (c *FloatingIPClient) GetByName(ctx context.Context, name string) (*FloatingIP, *Response, error) { - if name == "" { - return nil, nil, nil - } - floatingIPs, response, err := c.List(ctx, FloatingIPListOpts{Name: name}) - if len(floatingIPs) == 0 { - return nil, response, err - } - return floatingIPs[0], response, err + return firstByName(name, func() ([]*FloatingIP, *Response, error) { + return c.List(ctx, FloatingIPListOpts{Name: name}) + }) } // Get retrieves a Floating IP by its ID if the input can be parsed as an integer, otherwise it // retrieves a Floating IP by its name. If the Floating IP does not exist, nil is returned. func (c *FloatingIPClient) Get(ctx context.Context, idOrName string) (*FloatingIP, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByName(ctx, idOrName) + return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName) } // FloatingIPListOpts specifies options for listing Floating IPs. @@ -157,22 +141,17 @@ func (l FloatingIPListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *FloatingIPClient) List(ctx context.Context, opts FloatingIPListOpts) ([]*FloatingIP, *Response, error) { - path := "/floating_ips?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/floating_ips?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) - var body schema.FloatingIPListResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.FloatingIPListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - floatingIPs := make([]*FloatingIP, 0, len(body.FloatingIPs)) - for _, s := range body.FloatingIPs { - floatingIPs = append(floatingIPs, FloatingIPFromSchema(s)) + return nil, resp, err } - return floatingIPs, resp, nil + + return allFromSchemaFunc(respBody.FloatingIPs, FloatingIPFromSchema), resp, nil } // All returns all Floating IPs. @@ -182,22 +161,10 @@ func (c *FloatingIPClient) All(ctx context.Context) ([]*FloatingIP, error) { // AllWithOpts returns all Floating IPs for the given options. func (c *FloatingIPClient) AllWithOpts(ctx context.Context, opts FloatingIPListOpts) ([]*FloatingIP, error) { - allFloatingIPs := []*FloatingIP{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*FloatingIP, *Response, error) { opts.Page = page - floatingIPs, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allFloatingIPs = append(allFloatingIPs, floatingIPs...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allFloatingIPs, nil } // FloatingIPCreateOpts specifies options for creating a Floating IP. @@ -216,10 +183,10 @@ func (o FloatingIPCreateOpts) Validate() error { case FloatingIPTypeIPv4, FloatingIPTypeIPv6: break default: - return errors.New("missing or invalid type") + return invalidFieldValue(o, "Type", o.Type) } if o.HomeLocation == nil && o.Server == nil { - return errors.New("one of home location or server is required") + return missingOneOfFields(o, "HomeLocation", "Server") } return nil } @@ -232,8 +199,15 @@ type FloatingIPCreateResult struct { // Create creates a Floating IP. func (c *FloatingIPClient) Create(ctx context.Context, opts FloatingIPCreateOpts) (FloatingIPCreateResult, *Response, error) { + result := FloatingIPCreateResult{} + + const opPath = "/floating_ips" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := opPath + if err := opts.Validate(); err != nil { - return FloatingIPCreateResult{}, nil, err + return result, nil, err } reqBody := schema.FloatingIPCreateRequest{ @@ -250,38 +224,28 @@ func (c *FloatingIPClient) Create(ctx context.Context, opts FloatingIPCreateOpts if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return FloatingIPCreateResult{}, nil, err - } - req, err := c.client.NewRequest(ctx, "POST", "/floating_ips", bytes.NewReader(reqBodyData)) + respBody, resp, err := postRequest[schema.FloatingIPCreateResponse](ctx, c.client, reqPath, reqBody) if err != nil { - return FloatingIPCreateResult{}, nil, err + return result, resp, err } - var respBody schema.FloatingIPCreateResponse - resp, err := c.client.Do(req, &respBody) - if err != nil { - return FloatingIPCreateResult{}, resp, err - } - var action *Action + result.FloatingIP = FloatingIPFromSchema(respBody.FloatingIP) if respBody.Action != nil { - action = ActionFromSchema(*respBody.Action) + result.Action = ActionFromSchema(*respBody.Action) } - return FloatingIPCreateResult{ - FloatingIP: FloatingIPFromSchema(respBody.FloatingIP), - Action: action, - }, resp, nil + + return result, resp, nil } // Delete deletes a Floating IP. func (c *FloatingIPClient) Delete(ctx context.Context, floatingIP *FloatingIP) (*Response, error) { - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/floating_ips/%d", floatingIP.ID), nil) - if err != nil { - return nil, err - } - return c.client.Do(req, nil) + const opPath = "/floating_ips/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, floatingIP.ID) + + return deleteRequestNoResult(ctx, c.client, reqPath) } // FloatingIPUpdateOpts specifies options for updating a Floating IP. @@ -293,6 +257,11 @@ type FloatingIPUpdateOpts struct { // Update updates a Floating IP. func (c *FloatingIPClient) Update(ctx context.Context, floatingIP *FloatingIP, opts FloatingIPUpdateOpts) (*FloatingIP, *Response, error) { + const opPath = "/floating_ips/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, floatingIP.ID) + reqBody := schema.FloatingIPUpdateRequest{ Description: opts.Description, Name: opts.Name, @@ -300,68 +269,48 @@ func (c *FloatingIPClient) Update(ctx context.Context, floatingIP *FloatingIP, o if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/floating_ips/%d", floatingIP.ID) - req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.FloatingIPUpdateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := putRequest[schema.FloatingIPUpdateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return FloatingIPFromSchema(respBody.FloatingIP), resp, nil } // Assign assigns a Floating IP to a server. func (c *FloatingIPClient) Assign(ctx context.Context, floatingIP *FloatingIP, server *Server) (*Action, *Response, error) { + const opPath = "/floating_ips/%d/actions/assign" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, floatingIP.ID) + reqBody := schema.FloatingIPActionAssignRequest{ Server: server.ID, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/floating_ips/%d/actions/assign", floatingIP.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - var respBody schema.FloatingIPActionAssignResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.FloatingIPActionAssignResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } // Unassign unassigns a Floating IP from the currently assigned server. func (c *FloatingIPClient) Unassign(ctx context.Context, floatingIP *FloatingIP) (*Action, *Response, error) { - var reqBody schema.FloatingIPActionUnassignRequest - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } + const opPath = "/floating_ips/%d/actions/unassign" + ctx = ctxutil.SetOpPath(ctx, opPath) - path := fmt.Sprintf("/floating_ips/%d/actions/unassign", floatingIP.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } + reqPath := fmt.Sprintf(opPath, floatingIP.ID) + + reqBody := schema.FloatingIPActionUnassignRequest{} - var respBody schema.FloatingIPActionUnassignResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.FloatingIPActionUnassignResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -382,24 +331,19 @@ type FloatingIPChangeProtectionOpts struct { // ChangeProtection changes the resource protection level of a Floating IP. func (c *FloatingIPClient) ChangeProtection(ctx context.Context, floatingIP *FloatingIP, opts FloatingIPChangeProtectionOpts) (*Action, *Response, error) { + const opPath = "/floating_ips/%d/actions/change_protection" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, floatingIP.ID) + reqBody := schema.FloatingIPActionChangeProtectionRequest{ Delete: opts.Delete, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/floating_ips/%d/actions/change_protection", floatingIP.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.FloatingIPActionChangeProtectionResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.FloatingIPActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/hcloud.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/hcloud.go index 2c6745c2e85e49d994a450cf6ca44236b902cd37..6b1d6fde0156716f3a41adaf0e5b87a67ee1f138 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/hcloud.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/hcloud.go @@ -1,5 +1,34 @@ -// Package hcloud is a library for the Hetzner Cloud API. +/* +Package hcloud is a library for the Hetzner Cloud API. + +The Hetzner Cloud API reference is available at https://docs.hetzner.cloud. + +Make sure to follow our API changelog available at https://docs.hetzner.cloud/changelog +(or the RRS feed available at https://docs.hetzner.cloud/changelog/feed.rss) to be +notified about additions, deprecations and removals. + +# Retry mechanism + +The [Client.Do] method will retry failed requests that match certain criteria. The +default retry interval is defined by an exponential backoff algorithm truncated to 60s +with jitter. The default maximal number of retries is 5. + +The following rules defines when a request can be retried: + +When the [http.Client] returned a network timeout error. + +When the API returned an HTTP error, with the status code: + - [http.StatusBadGateway] + - [http.StatusGatewayTimeout] + +When the API returned an application error, with the code: + - [ErrorCodeConflict] + - [ErrorCodeRateLimitExceeded] + +Changes to the retry policy might occur between releases, and will not be considered +breaking changes. +*/ package hcloud // Version is the library's version following Semantic Versioning. -const Version = "2.8.0" // x-release-please-version +const Version = "2.21.1" // x-releaser-pleaser-version diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/image.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/image.go index 1da240170ec4b2544133bab5cbe94a82f6c59857..16454da1cce7a7216e5db2c732d7feead03fc887 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/image.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/image.go @@ -1,14 +1,13 @@ package hcloud import ( - "bytes" "context" - "encoding/json" "fmt" "net/url" "strconv" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -83,34 +82,29 @@ type ImageClient struct { // GetByID retrieves an image by its ID. If the image does not exist, nil is returned. func (c *ImageClient) GetByID(ctx context.Context, id int64) (*Image, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/images/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/images/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.ImageGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.ImageGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return ImageFromSchema(body.Image), resp, nil + + return ImageFromSchema(respBody.Image), resp, nil } // GetByName retrieves an image by its name. If the image does not exist, nil is returned. // // Deprecated: Use [ImageClient.GetByNameAndArchitecture] instead. func (c *ImageClient) GetByName(ctx context.Context, name string) (*Image, *Response, error) { - if name == "" { - return nil, nil, nil - } - images, response, err := c.List(ctx, ImageListOpts{Name: name}) - if len(images) == 0 { - return nil, response, err - } - return images[0], response, err + return firstByName(name, func() ([]*Image, *Response, error) { + return c.List(ctx, ImageListOpts{Name: name}) + }) } // GetByNameAndArchitecture retrieves an image by its name and architecture. If the image does not exist, @@ -118,14 +112,9 @@ func (c *ImageClient) GetByName(ctx context.Context, name string) (*Image, *Resp // In contrast to [ImageClient.Get], this method also returns deprecated images. Depending on your needs you should // check for this in your calling method. func (c *ImageClient) GetByNameAndArchitecture(ctx context.Context, name string, architecture Architecture) (*Image, *Response, error) { - if name == "" { - return nil, nil, nil - } - images, response, err := c.List(ctx, ImageListOpts{Name: name, Architecture: []Architecture{architecture}, IncludeDeprecated: true}) - if len(images) == 0 { - return nil, response, err - } - return images[0], response, err + return firstByName(name, func() ([]*Image, *Response, error) { + return c.List(ctx, ImageListOpts{Name: name, Architecture: []Architecture{architecture}, IncludeDeprecated: true}) + }) } // Get retrieves an image by its ID if the input can be parsed as an integer, otherwise it @@ -133,10 +122,7 @@ func (c *ImageClient) GetByNameAndArchitecture(ctx context.Context, name string, // // Deprecated: Use [ImageClient.GetForArchitecture] instead. func (c *ImageClient) Get(ctx context.Context, idOrName string) (*Image, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByName(ctx, idOrName) + return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName) } // GetForArchitecture retrieves an image by its ID if the input can be parsed as an integer, otherwise it @@ -145,10 +131,13 @@ func (c *ImageClient) Get(ctx context.Context, idOrName string) (*Image, *Respon // In contrast to [ImageClient.Get], this method also returns deprecated images. Depending on your needs you should // check for this in your calling method. func (c *ImageClient) GetForArchitecture(ctx context.Context, idOrName string, architecture Architecture) (*Image, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByNameAndArchitecture(ctx, idOrName, architecture) + return getByIDOrName(ctx, + c.GetByID, + func(ctx context.Context, name string) (*Image, *Response, error) { + return c.GetByNameAndArchitecture(ctx, name, architecture) + }, + idOrName, + ) } // ImageListOpts specifies options for listing images. @@ -194,22 +183,17 @@ func (l ImageListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *ImageClient) List(ctx context.Context, opts ImageListOpts) ([]*Image, *Response, error) { - path := "/images?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/images?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) - var body schema.ImageListResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.ImageListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - images := make([]*Image, 0, len(body.Images)) - for _, i := range body.Images { - images = append(images, ImageFromSchema(i)) + return nil, resp, err } - return images, resp, nil + + return allFromSchemaFunc(respBody.Images, ImageFromSchema), resp, nil } // All returns all images. @@ -219,31 +203,20 @@ func (c *ImageClient) All(ctx context.Context) ([]*Image, error) { // AllWithOpts returns all images for the given options. func (c *ImageClient) AllWithOpts(ctx context.Context, opts ImageListOpts) ([]*Image, error) { - allImages := []*Image{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*Image, *Response, error) { opts.Page = page - images, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allImages = append(allImages, images...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allImages, nil } // Delete deletes an image. func (c *ImageClient) Delete(ctx context.Context, image *Image) (*Response, error) { - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/images/%d", image.ID), nil) - if err != nil { - return nil, err - } - return c.client.Do(req, nil) + const opPath = "/images/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, image.ID) + + return deleteRequestNoResult(ctx, c.client, reqPath) } // ImageUpdateOpts specifies options for updating an image. @@ -255,6 +228,11 @@ type ImageUpdateOpts struct { // Update updates an image. func (c *ImageClient) Update(ctx context.Context, image *Image, opts ImageUpdateOpts) (*Image, *Response, error) { + const opPath = "/images/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, image.ID) + reqBody := schema.ImageUpdateRequest{ Description: opts.Description, } @@ -264,22 +242,12 @@ func (c *ImageClient) Update(ctx context.Context, image *Image, opts ImageUpdate if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/images/%d", image.ID) - req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.ImageUpdateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := putRequest[schema.ImageUpdateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ImageFromSchema(respBody.Image), resp, nil } @@ -290,24 +258,19 @@ type ImageChangeProtectionOpts struct { // ChangeProtection changes the resource protection level of an image. func (c *ImageClient) ChangeProtection(ctx context.Context, image *Image, opts ImageChangeProtectionOpts) (*Action, *Response, error) { + const opPath = "/images/%d/actions/change_protection" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, image.ID) + reqBody := schema.ImageActionChangeProtectionRequest{ Delete: opts.Delete, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/images/%d/actions/change_protection", image.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - respBody := schema.ImageActionChangeProtectionResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ImageActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation/metrics.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation/metrics.go index e52bd606a13dd1c0f18adf5a0f575ccf26586033..da4c0af0f158a7995c580eeec45c9be4d1eb1a43 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation/metrics.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation/metrics.go @@ -1,6 +1,7 @@ package instrumentation import ( + "errors" "fmt" "net/http" "regexp" @@ -9,6 +10,8 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" + + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" ) type Instrumenter struct { @@ -22,7 +25,12 @@ func New(subsystemIdentifier string, instrumentationRegistry prometheus.Register } // InstrumentedRoundTripper returns an instrumented round tripper. -func (i *Instrumenter) InstrumentedRoundTripper() http.RoundTripper { +func (i *Instrumenter) InstrumentedRoundTripper(transport http.RoundTripper) http.RoundTripper { + // By default, http client would use DefaultTransport on nil, but we internally are relying on it being configured + if transport == nil { + transport = http.DefaultTransport + } + inFlightRequestsGauge := registerOrReuse( i.instrumentationRegistry, prometheus.NewGauge(prometheus.GaugeOpts{ @@ -57,7 +65,7 @@ func (i *Instrumenter) InstrumentedRoundTripper() http.RoundTripper { return promhttp.InstrumentRoundTripperInFlight(inFlightRequestsGauge, promhttp.InstrumentRoundTripperDuration(requestLatencyHistogram, i.instrumentRoundTripperEndpoint(requestsPerEndpointCounter, - http.DefaultTransport, + transport, ), ), ) @@ -73,8 +81,17 @@ func (i *Instrumenter) instrumentRoundTripperEndpoint(counter *prometheus.Counte return func(r *http.Request) (*http.Response, error) { resp, err := next.RoundTrip(r) if err == nil { - statusCode := strconv.Itoa(resp.StatusCode) - counter.WithLabelValues(statusCode, strings.ToLower(resp.Request.Method), preparePathForLabel(resp.Request.URL.Path)).Inc() + apiEndpoint := ctxutil.OpPath(r.Context()) + // If the request does not set the operation path, we must construct it. Happens e.g. for + // user crafted requests. + if apiEndpoint == "" { + apiEndpoint = preparePathForLabel(resp.Request.URL.Path) + } + counter.WithLabelValues( + strconv.Itoa(resp.StatusCode), + strings.ToLower(resp.Request.Method), + apiEndpoint, + ).Inc() } return resp, err @@ -87,9 +104,10 @@ func (i *Instrumenter) instrumentRoundTripperEndpoint(counter *prometheus.Counte func registerOrReuse[C prometheus.Collector](registry prometheus.Registerer, collector C) C { err := registry.Register(collector) if err != nil { + var arErr prometheus.AlreadyRegisteredError // If we get a AlreadyRegisteredError we can return the existing collector - if are, ok := err.(prometheus.AlreadyRegisteredError); ok { - if existingCollector, ok := are.ExistingCollector.(C); ok { + if errors.As(err, &arErr) { + if existingCollector, ok := arErr.ExistingCollector.(C); ok { collector = existingCollector } else { panic("received incompatible existing collector") @@ -102,16 +120,16 @@ func registerOrReuse[C prometheus.Collector](registry prometheus.Registerer, col return collector } +var pathLabelRegexp = regexp.MustCompile("[^a-z/_]+") + func preparePathForLabel(path string) string { path = strings.ToLower(path) - // replace all numbers and chars that are not a-z, / or _ - reg := regexp.MustCompile("[^a-z/_]+") - path = reg.ReplaceAllString(path, "") + // replace the /v1/ that indicated the API version + path, _ = strings.CutPrefix(path, "/v1") - // replace all artifacts of number replacement (//) - path = strings.ReplaceAll(path, "//", "/") + // replace all numbers and chars that are not a-z, / or _ + path = pathLabelRegexp.ReplaceAllString(path, "-") - // replace the /v/ that indicated the API version - return strings.Replace(path, "/v/", "/", 1) + return path } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/iso.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/iso.go index 896a3ecdbb9137a902bc5e2de2b8c1bd7e6589d7..7aa8ab3a43b105a8d510913edae5a96503b7c33a 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/iso.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/iso.go @@ -4,9 +4,9 @@ import ( "context" "fmt" "net/url" - "strconv" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -40,40 +40,32 @@ type ISOClient struct { // GetByID retrieves an ISO by its ID. func (c *ISOClient) GetByID(ctx context.Context, id int64) (*ISO, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/isos/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/isos/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.ISOGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.ISOGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } return nil, resp, err } - return ISOFromSchema(body.ISO), resp, nil + + return ISOFromSchema(respBody.ISO), resp, nil } // GetByName retrieves an ISO by its name. func (c *ISOClient) GetByName(ctx context.Context, name string) (*ISO, *Response, error) { - if name == "" { - return nil, nil, nil - } - isos, response, err := c.List(ctx, ISOListOpts{Name: name}) - if len(isos) == 0 { - return nil, response, err - } - return isos[0], response, err + return firstByName(name, func() ([]*ISO, *Response, error) { + return c.List(ctx, ISOListOpts{Name: name}) + }) } // Get retrieves an ISO by its ID if the input can be parsed as an integer, otherwise it retrieves an ISO by its name. func (c *ISOClient) Get(ctx context.Context, idOrName string) (*ISO, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByName(ctx, idOrName) + return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName) } // ISOListOpts specifies options for listing isos. @@ -115,22 +107,17 @@ func (l ISOListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *ISOClient) List(ctx context.Context, opts ISOListOpts) ([]*ISO, *Response, error) { - path := "/isos?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/isos?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) - var body schema.ISOListResponse - resp, err := c.client.Do(req, &body) + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) + + respBody, resp, err := getRequest[schema.ISOListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - isos := make([]*ISO, 0, len(body.ISOs)) - for _, i := range body.ISOs { - isos = append(isos, ISOFromSchema(i)) + return nil, resp, err } - return isos, resp, nil + + return allFromSchemaFunc(respBody.ISOs, ISOFromSchema), resp, nil } // All returns all ISOs. @@ -140,20 +127,8 @@ func (c *ISOClient) All(ctx context.Context) ([]*ISO, error) { // AllWithOpts returns all ISOs for the given options. func (c *ISOClient) AllWithOpts(ctx context.Context, opts ISOListOpts) ([]*ISO, error) { - allISOs := []*ISO{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*ISO, *Response, error) { opts.Page = page - isos, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allISOs = append(allISOs, isos...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allISOs, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer.go index 471eb947ad9c653e69b0836e7a4e5c6ef5a353fd..bcfbb230712aa75df51b43ff59e44409687205d7 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer.go @@ -1,16 +1,14 @@ package hcloud import ( - "bytes" "context" - "encoding/json" "fmt" "net" - "net/http" "net/url" "strconv" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -200,26 +198,21 @@ type LoadBalancerProtection struct { // changeDNSPtr changes or resets the reverse DNS pointer for an IP address. // Pass a nil ptr to reset the reverse DNS pointer to its default value. func (lb *LoadBalancer) changeDNSPtr(ctx context.Context, client *Client, ip net.IP, ptr *string) (*Action, *Response, error) { + const opPath = "/load_balancers/%d/actions/change_dns_ptr" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, lb.ID) + reqBody := schema.LoadBalancerActionChangeDNSPtrRequest{ IP: ip.String(), DNSPtr: ptr, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/load_balancers/%d/actions/change_dns_ptr", lb.ID) - req, err := client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - respBody := schema.LoadBalancerActionChangeDNSPtrResponse{} - resp, err := client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.LoadBalancerActionChangeDNSPtrResponse](ctx, client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -243,41 +236,33 @@ type LoadBalancerClient struct { // GetByID retrieves a Load Balancer by its ID. If the Load Balancer does not exist, nil is returned. func (c *LoadBalancerClient) GetByID(ctx context.Context, id int64) (*LoadBalancer, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/load_balancers/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/load_balancers/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) - var body schema.LoadBalancerGetResponse - resp, err := c.client.Do(req, &body) + reqPath := fmt.Sprintf(opPath, id) + + respBody, resp, err := getRequest[schema.LoadBalancerGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return LoadBalancerFromSchema(body.LoadBalancer), resp, nil + + return LoadBalancerFromSchema(respBody.LoadBalancer), resp, nil } // GetByName retrieves a Load Balancer by its name. If the Load Balancer does not exist, nil is returned. func (c *LoadBalancerClient) GetByName(ctx context.Context, name string) (*LoadBalancer, *Response, error) { - if name == "" { - return nil, nil, nil - } - LoadBalancer, response, err := c.List(ctx, LoadBalancerListOpts{Name: name}) - if len(LoadBalancer) == 0 { - return nil, response, err - } - return LoadBalancer[0], response, err + return firstByName(name, func() ([]*LoadBalancer, *Response, error) { + return c.List(ctx, LoadBalancerListOpts{Name: name}) + }) } // Get retrieves a Load Balancer by its ID if the input can be parsed as an integer, otherwise it // retrieves a Load Balancer by its name. If the Load Balancer does not exist, nil is returned. func (c *LoadBalancerClient) Get(ctx context.Context, idOrName string) (*LoadBalancer, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByName(ctx, idOrName) + return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName) } // LoadBalancerListOpts specifies options for listing Load Balancers. @@ -303,22 +288,17 @@ func (l LoadBalancerListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *LoadBalancerClient) List(ctx context.Context, opts LoadBalancerListOpts) ([]*LoadBalancer, *Response, error) { - path := "/load_balancers?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/load_balancers?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) - var body schema.LoadBalancerListResponse - resp, err := c.client.Do(req, &body) + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) + + respBody, resp, err := getRequest[schema.LoadBalancerListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - LoadBalancers := make([]*LoadBalancer, 0, len(body.LoadBalancers)) - for _, s := range body.LoadBalancers { - LoadBalancers = append(LoadBalancers, LoadBalancerFromSchema(s)) + return nil, resp, err } - return LoadBalancers, resp, nil + + return allFromSchemaFunc(respBody.LoadBalancers, LoadBalancerFromSchema), resp, nil } // All returns all Load Balancers. @@ -328,22 +308,10 @@ func (c *LoadBalancerClient) All(ctx context.Context) ([]*LoadBalancer, error) { // AllWithOpts returns all Load Balancers for the given options. func (c *LoadBalancerClient) AllWithOpts(ctx context.Context, opts LoadBalancerListOpts) ([]*LoadBalancer, error) { - allLoadBalancers := []*LoadBalancer{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*LoadBalancer, *Response, error) { opts.Page = page - LoadBalancers, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allLoadBalancers = append(allLoadBalancers, LoadBalancers...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allLoadBalancers, nil } // LoadBalancerUpdateOpts specifies options for updating a Load Balancer. @@ -354,6 +322,11 @@ type LoadBalancerUpdateOpts struct { // Update updates a Load Balancer. func (c *LoadBalancerClient) Update(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerUpdateOpts) (*LoadBalancer, *Response, error) { + const opPath = "/load_balancers/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) + reqBody := schema.LoadBalancerUpdateRequest{} if opts.Name != "" { reqBody.Name = &opts.Name @@ -361,22 +334,12 @@ func (c *LoadBalancerClient) Update(ctx context.Context, loadBalancer *LoadBalan if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/load_balancers/%d", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.LoadBalancerUpdateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := putRequest[schema.LoadBalancerUpdateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return LoadBalancerFromSchema(respBody.LoadBalancer), resp, nil } @@ -472,73 +435,61 @@ type LoadBalancerCreateResult struct { // Create creates a new Load Balancer. func (c *LoadBalancerClient) Create(ctx context.Context, opts LoadBalancerCreateOpts) (LoadBalancerCreateResult, *Response, error) { + const opPath = "/load_balancers" + ctx = ctxutil.SetOpPath(ctx, opPath) + + result := LoadBalancerCreateResult{} + + reqPath := opPath + reqBody := loadBalancerCreateOptsToSchema(opts) - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return LoadBalancerCreateResult{}, nil, err - } - req, err := c.client.NewRequest(ctx, "POST", "/load_balancers", bytes.NewReader(reqBodyData)) - if err != nil { - return LoadBalancerCreateResult{}, nil, err - } - respBody := schema.LoadBalancerCreateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.LoadBalancerCreateResponse](ctx, c.client, reqPath, reqBody) if err != nil { - return LoadBalancerCreateResult{}, resp, err + return result, resp, err } - return LoadBalancerCreateResult{ - LoadBalancer: LoadBalancerFromSchema(respBody.LoadBalancer), - Action: ActionFromSchema(respBody.Action), - }, resp, nil + + result.LoadBalancer = LoadBalancerFromSchema(respBody.LoadBalancer) + result.Action = ActionFromSchema(respBody.Action) + + return result, resp, nil } // Delete deletes a Load Balancer. func (c *LoadBalancerClient) Delete(ctx context.Context, loadBalancer *LoadBalancer) (*Response, error) { - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/load_balancers/%d", loadBalancer.ID), nil) - if err != nil { - return nil, err - } - return c.client.Do(req, nil) + const opPath = "/load_balancers/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) + + return deleteRequestNoResult(ctx, c.client, reqPath) } func (c *LoadBalancerClient) addTarget(ctx context.Context, loadBalancer *LoadBalancer, reqBody schema.LoadBalancerActionAddTargetRequest) (*Action, *Response, error) { - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } + const opPath = "/load_balancers/%d/actions/add_target" + ctx = ctxutil.SetOpPath(ctx, opPath) - path := fmt.Sprintf("/load_balancers/%d/actions/add_target", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) - var respBody schema.LoadBalancerActionAddTargetResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.LoadBalancerActionAddTargetResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } func (c *LoadBalancerClient) removeTarget(ctx context.Context, loadBalancer *LoadBalancer, reqBody schema.LoadBalancerActionRemoveTargetRequest) (*Action, *Response, error) { - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } + const opPath = "/load_balancers/%d/actions/remove_target" + ctx = ctxutil.SetOpPath(ctx, opPath) - path := fmt.Sprintf("/load_balancers/%d/actions/remove_target", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) - var respBody schema.LoadBalancerActionRemoveTargetResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.LoadBalancerActionRemoveTargetResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -671,23 +622,18 @@ type LoadBalancerAddServiceOptsHealthCheckHTTP struct { // AddService adds a service to a Load Balancer. func (c *LoadBalancerClient) AddService(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerAddServiceOpts) (*Action, *Response, error) { - reqBody := loadBalancerAddServiceOptsToSchema(opts) - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } + const opPath = "/load_balancers/%d/actions/add_service" + ctx = ctxutil.SetOpPath(ctx, opPath) - path := fmt.Sprintf("/load_balancers/%d/actions/add_service", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) - var respBody schema.LoadBalancerActionAddServiceResponse - resp, err := c.client.Do(req, &respBody) + reqBody := loadBalancerAddServiceOptsToSchema(opts) + + respBody, resp, err := postRequest[schema.LoadBalancerActionAddServiceResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -732,48 +678,38 @@ type LoadBalancerUpdateServiceOptsHealthCheckHTTP struct { // UpdateService updates a Load Balancer service. func (c *LoadBalancerClient) UpdateService(ctx context.Context, loadBalancer *LoadBalancer, listenPort int, opts LoadBalancerUpdateServiceOpts) (*Action, *Response, error) { + const opPath = "/load_balancers/%d/actions/update_service" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) + reqBody := loadBalancerUpdateServiceOptsToSchema(opts) reqBody.ListenPort = listenPort - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/load_balancers/%d/actions/update_service", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - var respBody schema.LoadBalancerActionUpdateServiceResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.LoadBalancerActionUpdateServiceResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } // DeleteService deletes a Load Balancer service. func (c *LoadBalancerClient) DeleteService(ctx context.Context, loadBalancer *LoadBalancer, listenPort int) (*Action, *Response, error) { + const opPath = "/load_balancers/%d/actions/delete_service" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) + reqBody := schema.LoadBalancerDeleteServiceRequest{ ListenPort: listenPort, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/load_balancers/%d/actions/delete_service", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - var respBody schema.LoadBalancerDeleteServiceResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.LoadBalancerDeleteServiceResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -784,26 +720,21 @@ type LoadBalancerChangeProtectionOpts struct { // ChangeProtection changes the resource protection level of a Load Balancer. func (c *LoadBalancerClient) ChangeProtection(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerChangeProtectionOpts) (*Action, *Response, error) { + const opPath = "/load_balancers/%d/actions/change_protection" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) + reqBody := schema.LoadBalancerActionChangeProtectionRequest{ Delete: opts.Delete, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/load_balancers/%d/actions/change_protection", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.LoadBalancerActionChangeProtectionResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.LoadBalancerActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } // LoadBalancerChangeAlgorithmOpts specifies options for changing the algorithm of a Load Balancer. @@ -813,26 +744,21 @@ type LoadBalancerChangeAlgorithmOpts struct { // ChangeAlgorithm changes the algorithm of a Load Balancer. func (c *LoadBalancerClient) ChangeAlgorithm(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerChangeAlgorithmOpts) (*Action, *Response, error) { + const opPath = "/load_balancers/%d/actions/change_algorithm" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) + reqBody := schema.LoadBalancerActionChangeAlgorithmRequest{ Type: string(opts.Type), } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/load_balancers/%d/actions/change_algorithm", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.LoadBalancerActionChangeAlgorithmResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.LoadBalancerActionChangeAlgorithmResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } // LoadBalancerAttachToNetworkOpts specifies options for attaching a Load Balancer to a network. @@ -843,29 +769,24 @@ type LoadBalancerAttachToNetworkOpts struct { // AttachToNetwork attaches a Load Balancer to a network. func (c *LoadBalancerClient) AttachToNetwork(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerAttachToNetworkOpts) (*Action, *Response, error) { + const opPath = "/load_balancers/%d/actions/attach_to_network" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) + reqBody := schema.LoadBalancerActionAttachToNetworkRequest{ Network: opts.Network.ID, } if opts.IP != nil { reqBody.IP = Ptr(opts.IP.String()) } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/load_balancers/%d/actions/attach_to_network", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.LoadBalancerActionAttachToNetworkResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.LoadBalancerActionAttachToNetworkResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } // LoadBalancerDetachFromNetworkOpts specifies options for detaching a Load Balancer from a network. @@ -875,56 +796,51 @@ type LoadBalancerDetachFromNetworkOpts struct { // DetachFromNetwork detaches a Load Balancer from a network. func (c *LoadBalancerClient) DetachFromNetwork(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerDetachFromNetworkOpts) (*Action, *Response, error) { + const opPath = "/load_balancers/%d/actions/detach_from_network" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) + reqBody := schema.LoadBalancerActionDetachFromNetworkRequest{ Network: opts.Network.ID, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/load_balancers/%d/actions/detach_from_network", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.LoadBalancerActionDetachFromNetworkResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.LoadBalancerActionDetachFromNetworkResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } // EnablePublicInterface enables the Load Balancer's public network interface. func (c *LoadBalancerClient) EnablePublicInterface(ctx context.Context, loadBalancer *LoadBalancer) (*Action, *Response, error) { - path := fmt.Sprintf("/load_balancers/%d/actions/enable_public_interface", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return nil, nil, err - } - respBody := schema.LoadBalancerActionEnablePublicInterfaceResponse{} - resp, err := c.client.Do(req, &respBody) + const opPath = "/load_balancers/%d/actions/enable_public_interface" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) + + respBody, resp, err := postRequest[schema.LoadBalancerActionEnablePublicInterfaceResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } // DisablePublicInterface disables the Load Balancer's public network interface. func (c *LoadBalancerClient) DisablePublicInterface(ctx context.Context, loadBalancer *LoadBalancer) (*Action, *Response, error) { - path := fmt.Sprintf("/load_balancers/%d/actions/disable_public_interface", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return nil, nil, err - } - respBody := schema.LoadBalancerActionDisablePublicInterfaceResponse{} - resp, err := c.client.Do(req, &respBody) + const opPath = "/load_balancers/%d/actions/disable_public_interface" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) + + respBody, resp, err := postRequest[schema.LoadBalancerActionDisablePublicInterfaceResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } // LoadBalancerChangeTypeOpts specifies options for changing a Load Balancer's type. @@ -934,28 +850,21 @@ type LoadBalancerChangeTypeOpts struct { // ChangeType changes a Load Balancer's type. func (c *LoadBalancerClient) ChangeType(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerChangeTypeOpts) (*Action, *Response, error) { - reqBody := schema.LoadBalancerActionChangeTypeRequest{} - if opts.LoadBalancerType.ID != 0 { - reqBody.LoadBalancerType = opts.LoadBalancerType.ID - } else { - reqBody.LoadBalancerType = opts.LoadBalancerType.Name - } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } + const opPath = "/load_balancers/%d/actions/change_type" + ctx = ctxutil.SetOpPath(ctx, opPath) - path := fmt.Sprintf("/load_balancers/%d/actions/change_type", loadBalancer.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err + reqPath := fmt.Sprintf(opPath, loadBalancer.ID) + + reqBody := schema.LoadBalancerActionChangeTypeRequest{} + if opts.LoadBalancerType.ID != 0 || opts.LoadBalancerType.Name != "" { + reqBody.LoadBalancerType = schema.IDOrName{ID: opts.LoadBalancerType.ID, Name: opts.LoadBalancerType.Name} } - respBody := schema.LoadBalancerActionChangeTypeResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.LoadBalancerActionChangeTypeResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -980,32 +889,34 @@ type LoadBalancerGetMetricsOpts struct { Step int } -func (o *LoadBalancerGetMetricsOpts) addQueryParams(req *http.Request) error { - query := req.URL.Query() - +func (o LoadBalancerGetMetricsOpts) Validate() error { if len(o.Types) == 0 { - return fmt.Errorf("no metric types specified") + return missingField(o, "Types") + } + if o.Start.IsZero() { + return missingField(o, "Start") + } + if o.End.IsZero() { + return missingField(o, "End") } + return nil +} + +func (o LoadBalancerGetMetricsOpts) values() url.Values { + query := url.Values{} + for _, typ := range o.Types { query.Add("type", string(typ)) } - if o.Start.IsZero() { - return fmt.Errorf("no start time specified") - } query.Add("start", o.Start.Format(time.RFC3339)) - - if o.End.IsZero() { - return fmt.Errorf("no end time specified") - } query.Add("end", o.End.Format(time.RFC3339)) if o.Step > 0 { query.Add("step", strconv.Itoa(o.Step)) } - req.URL.RawQuery = query.Encode() - return nil + return query } // LoadBalancerMetrics contains the metrics requested for a Load Balancer. @@ -1024,31 +935,32 @@ type LoadBalancerMetricsValue struct { // GetMetrics obtains metrics for a Load Balancer. func (c *LoadBalancerClient) GetMetrics( - ctx context.Context, lb *LoadBalancer, opts LoadBalancerGetMetricsOpts, + ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerGetMetricsOpts, ) (*LoadBalancerMetrics, *Response, error) { - var respBody schema.LoadBalancerGetMetricsResponse + const opPath = "/load_balancers/%d/metrics?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) - if lb == nil { - return nil, nil, fmt.Errorf("illegal argument: load balancer is nil") + if loadBalancer == nil { + return nil, nil, missingArgument("loadBalancer", loadBalancer) } - path := fmt.Sprintf("/load_balancers/%d/metrics", lb.ID) - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, fmt.Errorf("new request: %v", err) - } - if err := opts.addQueryParams(req); err != nil { - return nil, nil, fmt.Errorf("add query params: %v", err) + if err := opts.Validate(); err != nil { + return nil, nil, err } - resp, err := c.client.Do(req, &respBody) + + reqPath := fmt.Sprintf(opPath, loadBalancer.ID, opts.values().Encode()) + + respBody, resp, err := getRequest[schema.LoadBalancerGetMetricsResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, fmt.Errorf("get metrics: %v", err) + return nil, resp, err } - ms, err := loadBalancerMetricsFromSchema(&respBody) + + metrics, err := loadBalancerMetricsFromSchema(&respBody) if err != nil { - return nil, nil, fmt.Errorf("convert response body: %v", err) + return nil, nil, fmt.Errorf("convert response body: %w", err) } - return ms, resp, nil + + return metrics, resp, nil } // ChangeDNSPtr changes or resets the reverse DNS pointer for a Load Balancer. diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer_type.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer_type.go index 00c852b5ecc3e0b9ec3993feb9901401b3d5c9c4..58ddbfa8f7dee5e68785b30f9c5b208c9834cc78 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer_type.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer_type.go @@ -6,6 +6,7 @@ import ( "net/url" "strconv" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -29,32 +30,27 @@ type LoadBalancerTypeClient struct { // GetByID retrieves a Load Balancer type by its ID. If the Load Balancer type does not exist, nil is returned. func (c *LoadBalancerTypeClient) GetByID(ctx context.Context, id int64) (*LoadBalancerType, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/load_balancer_types/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/load_balancer_types/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.LoadBalancerTypeGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.LoadBalancerTypeGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return LoadBalancerTypeFromSchema(body.LoadBalancerType), resp, nil + + return LoadBalancerTypeFromSchema(respBody.LoadBalancerType), resp, nil } // GetByName retrieves a Load Balancer type by its name. If the Load Balancer type does not exist, nil is returned. func (c *LoadBalancerTypeClient) GetByName(ctx context.Context, name string) (*LoadBalancerType, *Response, error) { - if name == "" { - return nil, nil, nil - } - LoadBalancerTypes, response, err := c.List(ctx, LoadBalancerTypeListOpts{Name: name}) - if len(LoadBalancerTypes) == 0 { - return nil, response, err - } - return LoadBalancerTypes[0], response, err + return firstByName(name, func() ([]*LoadBalancerType, *Response, error) { + return c.List(ctx, LoadBalancerTypeListOpts{Name: name}) + }) } // Get retrieves a Load Balancer type by its ID if the input can be parsed as an integer, otherwise it @@ -89,22 +85,17 @@ func (l LoadBalancerTypeListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *LoadBalancerTypeClient) List(ctx context.Context, opts LoadBalancerTypeListOpts) ([]*LoadBalancerType, *Response, error) { - path := "/load_balancer_types?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/load_balancer_types?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) - var body schema.LoadBalancerTypeListResponse - resp, err := c.client.Do(req, &body) + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) + + respBody, resp, err := getRequest[schema.LoadBalancerTypeListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - LoadBalancerTypes := make([]*LoadBalancerType, 0, len(body.LoadBalancerTypes)) - for _, s := range body.LoadBalancerTypes { - LoadBalancerTypes = append(LoadBalancerTypes, LoadBalancerTypeFromSchema(s)) + return nil, resp, err } - return LoadBalancerTypes, resp, nil + + return allFromSchemaFunc(respBody.LoadBalancerTypes, LoadBalancerTypeFromSchema), resp, nil } // All returns all Load Balancer types. @@ -114,20 +105,8 @@ func (c *LoadBalancerTypeClient) All(ctx context.Context) ([]*LoadBalancerType, // AllWithOpts returns all Load Balancer types for the given options. func (c *LoadBalancerTypeClient) AllWithOpts(ctx context.Context, opts LoadBalancerTypeListOpts) ([]*LoadBalancerType, error) { - allLoadBalancerTypes := []*LoadBalancerType{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*LoadBalancerType, *Response, error) { opts.Page = page - LoadBalancerTypes, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allLoadBalancerTypes = append(allLoadBalancerTypes, LoadBalancerTypes...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allLoadBalancerTypes, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/location.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/location.go index c4d75177503483b8216655dec6472ab2957b3d98..66fd7fe0f4b5bf079ecbc729bab4442a2263f0a7 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/location.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/location.go @@ -6,6 +6,7 @@ import ( "net/url" "strconv" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -28,32 +29,27 @@ type LocationClient struct { // GetByID retrieves a location by its ID. If the location does not exist, nil is returned. func (c *LocationClient) GetByID(ctx context.Context, id int64) (*Location, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/locations/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/locations/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.LocationGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.LocationGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } return nil, resp, err } - return LocationFromSchema(body.Location), resp, nil + + return LocationFromSchema(respBody.Location), resp, nil } // GetByName retrieves an location by its name. If the location does not exist, nil is returned. func (c *LocationClient) GetByName(ctx context.Context, name string) (*Location, *Response, error) { - if name == "" { - return nil, nil, nil - } - locations, response, err := c.List(ctx, LocationListOpts{Name: name}) - if len(locations) == 0 { - return nil, response, err - } - return locations[0], response, err + return firstByName(name, func() ([]*Location, *Response, error) { + return c.List(ctx, LocationListOpts{Name: name}) + }) } // Get retrieves a location by its ID if the input can be parsed as an integer, otherwise it @@ -88,22 +84,17 @@ func (l LocationListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *LocationClient) List(ctx context.Context, opts LocationListOpts) ([]*Location, *Response, error) { - path := "/locations?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/locations?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) - var body schema.LocationListResponse - resp, err := c.client.Do(req, &body) + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) + + respBody, resp, err := getRequest[schema.LocationListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - locations := make([]*Location, 0, len(body.Locations)) - for _, i := range body.Locations { - locations = append(locations, LocationFromSchema(i)) + return nil, resp, err } - return locations, resp, nil + + return allFromSchemaFunc(respBody.Locations, LocationFromSchema), resp, nil } // All returns all locations. @@ -113,20 +104,8 @@ func (c *LocationClient) All(ctx context.Context) ([]*Location, error) { // AllWithOpts returns all locations for the given options. func (c *LocationClient) AllWithOpts(ctx context.Context, opts LocationListOpts) ([]*Location, error) { - allLocations := []*Location{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*Location, *Response, error) { opts.Page = page - locations, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allLocations = append(allLocations, locations...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allLocations, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/metadata/client.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/metadata/client.go index 8aeb772f8514a213432a4a921ede1d5ae45a24c3..837aef7ddd13bde684e2a27d2f9c0a471372b59e 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/metadata/client.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/metadata/client.go @@ -1,6 +1,8 @@ package metadata import ( + "bytes" + "context" "fmt" "io" "net" @@ -11,6 +13,7 @@ import ( "github.com/prometheus/client_golang/prometheus" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation" ) @@ -72,24 +75,33 @@ func NewClient(options ...ClientOption) *Client { if client.instrumentationRegistry != nil { i := instrumentation.New("metadata", client.instrumentationRegistry) - client.httpClient.Transport = i.InstrumentedRoundTripper() + client.httpClient.Transport = i.InstrumentedRoundTripper(client.httpClient.Transport) } return client } // get executes an HTTP request against the API. func (c *Client) get(path string) (string, error) { - url := c.endpoint + path - resp, err := c.httpClient.Get(url) + ctx := ctxutil.SetOpPath(context.Background(), path) + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.endpoint+path, http.NoBody) + if err != nil { + return "", err + } + + resp, err := c.httpClient.Do(req) if err != nil { return "", err } + defer resp.Body.Close() bodyBytes, err := io.ReadAll(resp.Body) if err != nil { return "", err } - body := string(bodyBytes) + + body := string(bytes.TrimSpace(bodyBytes)) + if resp.StatusCode < 200 || resp.StatusCode >= 300 { return body, fmt.Errorf("response status was %d", resp.StatusCode) } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/network.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/network.go index d0452bc93ce447ee9ccc70c4dd1b91a23dd19fc9..8760f54780011f4c740fdd26ecc207bd07f4c2d4 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/network.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/network.go @@ -1,16 +1,13 @@ package hcloud import ( - "bytes" "context" - "encoding/json" - "errors" "fmt" "net" "net/url" - "strconv" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -19,9 +16,10 @@ type NetworkZone string // List of available Network Zones. const ( - NetworkZoneEUCentral NetworkZone = "eu-central" - NetworkZoneUSEast NetworkZone = "us-east" - NetworkZoneUSWest NetworkZone = "us-west" + NetworkZoneEUCentral NetworkZone = "eu-central" + NetworkZoneUSEast NetworkZone = "us-east" + NetworkZoneUSWest NetworkZone = "us-west" + NetworkZoneAPSouthEast NetworkZone = "ap-southeast" ) // NetworkSubnetType specifies a type of a subnet. @@ -29,22 +27,30 @@ type NetworkSubnetType string // List of available network subnet types. const ( - NetworkSubnetTypeCloud NetworkSubnetType = "cloud" - NetworkSubnetTypeServer NetworkSubnetType = "server" + // Used to connect cloud servers and load balancers. + NetworkSubnetTypeCloud NetworkSubnetType = "cloud" + // Used to connect cloud servers and load balancers. + // + // Deprecated: Use [NetworkSubnetTypeCloud] instead. + NetworkSubnetTypeServer NetworkSubnetType = "server" + // Used to connect cloud servers and load balancers with dedicated servers. + // + // See https://docs.hetzner.com/cloud/networks/connect-dedi-vswitch/ NetworkSubnetTypeVSwitch NetworkSubnetType = "vswitch" ) // Network represents a network in the Hetzner Cloud. type Network struct { - ID int64 - Name string - Created time.Time - IPRange *net.IPNet - Subnets []NetworkSubnet - Routes []NetworkRoute - Servers []*Server - Protection NetworkProtection - Labels map[string]string + ID int64 + Name string + Created time.Time + IPRange *net.IPNet + Subnets []NetworkSubnet + Routes []NetworkRoute + Servers []*Server + LoadBalancers []*LoadBalancer + Protection NetworkProtection + Labels map[string]string // ExposeRoutesToVSwitch indicates if the routes from this network should be exposed to the vSwitch connection. ExposeRoutesToVSwitch bool @@ -78,41 +84,33 @@ type NetworkClient struct { // GetByID retrieves a network by its ID. If the network does not exist, nil is returned. func (c *NetworkClient) GetByID(ctx context.Context, id int64) (*Network, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/networks/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/networks/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.NetworkGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.NetworkGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return NetworkFromSchema(body.Network), resp, nil + + return NetworkFromSchema(respBody.Network), resp, nil } // GetByName retrieves a network by its name. If the network does not exist, nil is returned. func (c *NetworkClient) GetByName(ctx context.Context, name string) (*Network, *Response, error) { - if name == "" { - return nil, nil, nil - } - Networks, response, err := c.List(ctx, NetworkListOpts{Name: name}) - if len(Networks) == 0 { - return nil, response, err - } - return Networks[0], response, err + return firstByName(name, func() ([]*Network, *Response, error) { + return c.List(ctx, NetworkListOpts{Name: name}) + }) } // Get retrieves a network by its ID if the input can be parsed as an integer, otherwise it // retrieves a network by its name. If the network does not exist, nil is returned. func (c *NetworkClient) Get(ctx context.Context, idOrName string) (*Network, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByName(ctx, idOrName) + return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName) } // NetworkListOpts specifies options for listing networks. @@ -138,22 +136,17 @@ func (l NetworkListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *NetworkClient) List(ctx context.Context, opts NetworkListOpts) ([]*Network, *Response, error) { - path := "/networks?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/networks?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) - var body schema.NetworkListResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.NetworkListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - Networks := make([]*Network, 0, len(body.Networks)) - for _, s := range body.Networks { - Networks = append(Networks, NetworkFromSchema(s)) + return nil, resp, err } - return Networks, resp, nil + + return allFromSchemaFunc(respBody.Networks, NetworkFromSchema), resp, nil } // All returns all networks. @@ -163,31 +156,20 @@ func (c *NetworkClient) All(ctx context.Context) ([]*Network, error) { // AllWithOpts returns all networks for the given options. func (c *NetworkClient) AllWithOpts(ctx context.Context, opts NetworkListOpts) ([]*Network, error) { - allNetworks := []*Network{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*Network, *Response, error) { opts.Page = page - Networks, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allNetworks = append(allNetworks, Networks...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allNetworks, nil } // Delete deletes a network. func (c *NetworkClient) Delete(ctx context.Context, network *Network) (*Response, error) { - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/networks/%d", network.ID), nil) - if err != nil { - return nil, err - } - return c.client.Do(req, nil) + const opPath = "/networks/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, network.ID) + + return deleteRequestNoResult(ctx, c.client, reqPath) } // NetworkUpdateOpts specifies options for updating a network. @@ -201,6 +183,11 @@ type NetworkUpdateOpts struct { // Update updates a network. func (c *NetworkClient) Update(ctx context.Context, network *Network, opts NetworkUpdateOpts) (*Network, *Response, error) { + const opPath = "/networks/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, network.ID) + reqBody := schema.NetworkUpdateRequest{ Name: opts.Name, } @@ -211,22 +198,11 @@ func (c *NetworkClient) Update(ctx context.Context, network *Network, opts Netwo reqBody.ExposeRoutesToVSwitch = opts.ExposeRoutesToVSwitch } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/networks/%d", network.ID) - req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - respBody := schema.NetworkUpdateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := putRequest[schema.NetworkUpdateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return NetworkFromSchema(respBody.Network), resp, nil } @@ -245,16 +221,21 @@ type NetworkCreateOpts struct { // Validate checks if options are valid. func (o NetworkCreateOpts) Validate() error { if o.Name == "" { - return errors.New("missing name") + return missingField(o, "Name") } if o.IPRange == nil || o.IPRange.String() == "" { - return errors.New("missing IP range") + return missingField(o, "IPRange") } return nil } // Create creates a new network. func (c *NetworkClient) Create(ctx context.Context, opts NetworkCreateOpts) (*Network, *Response, error) { + const opPath = "/networks" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := opPath + if err := opts.Validate(); err != nil { return nil, nil, err } @@ -283,20 +264,12 @@ func (c *NetworkClient) Create(ctx context.Context, opts NetworkCreateOpts) (*Ne if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - req, err := c.client.NewRequest(ctx, "POST", "/networks", bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.NetworkCreateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.NetworkCreateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return NetworkFromSchema(respBody.Network), resp, nil } @@ -307,25 +280,20 @@ type NetworkChangeIPRangeOpts struct { // ChangeIPRange changes the IP range of a network. func (c *NetworkClient) ChangeIPRange(ctx context.Context, network *Network, opts NetworkChangeIPRangeOpts) (*Action, *Response, error) { + const opPath = "/networks/%d/actions/change_ip_range" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, network.ID) + reqBody := schema.NetworkActionChangeIPRangeRequest{ IPRange: opts.IPRange.String(), } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/networks/%d/actions/change_ip_range", network.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.NetworkActionChangeIPRangeResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.NetworkActionChangeIPRangeResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -336,6 +304,11 @@ type NetworkAddSubnetOpts struct { // AddSubnet adds a subnet to a network. func (c *NetworkClient) AddSubnet(ctx context.Context, network *Network, opts NetworkAddSubnetOpts) (*Action, *Response, error) { + const opPath = "/networks/%d/actions/add_subnet" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, network.ID) + reqBody := schema.NetworkActionAddSubnetRequest{ Type: string(opts.Subnet.Type), NetworkZone: string(opts.Subnet.NetworkZone), @@ -346,22 +319,12 @@ func (c *NetworkClient) AddSubnet(ctx context.Context, network *Network, opts Ne if opts.Subnet.VSwitchID != 0 { reqBody.VSwitchID = opts.Subnet.VSwitchID } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/networks/%d/actions/add_subnet", network.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - respBody := schema.NetworkActionAddSubnetResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.NetworkActionAddSubnetResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -372,25 +335,20 @@ type NetworkDeleteSubnetOpts struct { // DeleteSubnet deletes a subnet from a network. func (c *NetworkClient) DeleteSubnet(ctx context.Context, network *Network, opts NetworkDeleteSubnetOpts) (*Action, *Response, error) { + const opPath = "/networks/%d/actions/delete_subnet" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, network.ID) + reqBody := schema.NetworkActionDeleteSubnetRequest{ IPRange: opts.Subnet.IPRange.String(), } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/networks/%d/actions/delete_subnet", network.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.NetworkActionDeleteSubnetResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.NetworkActionDeleteSubnetResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -401,26 +359,21 @@ type NetworkAddRouteOpts struct { // AddRoute adds a route to a network. func (c *NetworkClient) AddRoute(ctx context.Context, network *Network, opts NetworkAddRouteOpts) (*Action, *Response, error) { + const opPath = "/networks/%d/actions/add_route" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, network.ID) + reqBody := schema.NetworkActionAddRouteRequest{ Destination: opts.Route.Destination.String(), Gateway: opts.Route.Gateway.String(), } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/networks/%d/actions/add_route", network.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - respBody := schema.NetworkActionAddSubnetResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.NetworkActionAddRouteResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -431,26 +384,21 @@ type NetworkDeleteRouteOpts struct { // DeleteRoute deletes a route from a network. func (c *NetworkClient) DeleteRoute(ctx context.Context, network *Network, opts NetworkDeleteRouteOpts) (*Action, *Response, error) { + const opPath = "/networks/%d/actions/delete_route" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, network.ID) + reqBody := schema.NetworkActionDeleteRouteRequest{ Destination: opts.Route.Destination.String(), Gateway: opts.Route.Gateway.String(), } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/networks/%d/actions/delete_route", network.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - respBody := schema.NetworkActionDeleteSubnetResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.NetworkActionDeleteRouteResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -461,24 +409,19 @@ type NetworkChangeProtectionOpts struct { // ChangeProtection changes the resource protection level of a network. func (c *NetworkClient) ChangeProtection(ctx context.Context, network *Network, opts NetworkChangeProtectionOpts) (*Action, *Response, error) { + const opPath = "/networks/%d/actions/change_protection" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, network.ID) + reqBody := schema.NetworkActionChangeProtectionRequest{ Delete: opts.Delete, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/networks/%d/actions/change_protection", network.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - respBody := schema.NetworkActionChangeProtectionResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.NetworkActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/placement_group.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/placement_group.go index 6907ad4495cbd638de6701bf96800ad7d269bcc4..41224344025e84fb31d6a1b9fca2e75382e7377f 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/placement_group.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/placement_group.go @@ -1,15 +1,12 @@ package hcloud import ( - "bytes" "context" - "encoding/json" - "errors" "fmt" "net/url" - "strconv" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -38,41 +35,33 @@ type PlacementGroupClient struct { // GetByID retrieves a PlacementGroup by its ID. If the PlacementGroup does not exist, nil is returned. func (c *PlacementGroupClient) GetByID(ctx context.Context, id int64) (*PlacementGroup, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/placement_groups/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/placement_groups/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.PlacementGroupGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.PlacementGroupGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return PlacementGroupFromSchema(body.PlacementGroup), resp, nil + + return PlacementGroupFromSchema(respBody.PlacementGroup), resp, nil } // GetByName retrieves a PlacementGroup by its name. If the PlacementGroup does not exist, nil is returned. func (c *PlacementGroupClient) GetByName(ctx context.Context, name string) (*PlacementGroup, *Response, error) { - if name == "" { - return nil, nil, nil - } - placementGroups, response, err := c.List(ctx, PlacementGroupListOpts{Name: name}) - if len(placementGroups) == 0 { - return nil, response, err - } - return placementGroups[0], response, err + return firstByName(name, func() ([]*PlacementGroup, *Response, error) { + return c.List(ctx, PlacementGroupListOpts{Name: name}) + }) } // Get retrieves a PlacementGroup by its ID if the input can be parsed as an integer, otherwise it // retrieves a PlacementGroup by its name. If the PlacementGroup does not exist, nil is returned. func (c *PlacementGroupClient) Get(ctx context.Context, idOrName string) (*PlacementGroup, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByName(ctx, idOrName) + return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName) } // PlacementGroupListOpts specifies options for listing PlacementGroup. @@ -102,22 +91,17 @@ func (l PlacementGroupListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *PlacementGroupClient) List(ctx context.Context, opts PlacementGroupListOpts) ([]*PlacementGroup, *Response, error) { - path := "/placement_groups?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/placement_groups?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) - var body schema.PlacementGroupListResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.PlacementGroupListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - placementGroups := make([]*PlacementGroup, 0, len(body.PlacementGroups)) - for _, g := range body.PlacementGroups { - placementGroups = append(placementGroups, PlacementGroupFromSchema(g)) + return nil, resp, err } - return placementGroups, resp, nil + + return allFromSchemaFunc(respBody.PlacementGroups, PlacementGroupFromSchema), resp, nil } // All returns all PlacementGroups. @@ -133,22 +117,10 @@ func (c *PlacementGroupClient) All(ctx context.Context) ([]*PlacementGroup, erro // AllWithOpts returns all PlacementGroups for the given options. func (c *PlacementGroupClient) AllWithOpts(ctx context.Context, opts PlacementGroupListOpts) ([]*PlacementGroup, error) { - allPlacementGroups := []*PlacementGroup{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*PlacementGroup, *Response, error) { opts.Page = page - placementGroups, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allPlacementGroups = append(allPlacementGroups, placementGroups...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allPlacementGroups, nil } // PlacementGroupCreateOpts specifies options for creating a new PlacementGroup. @@ -161,7 +133,7 @@ type PlacementGroupCreateOpts struct { // Validate checks if options are valid. func (o PlacementGroupCreateOpts) Validate() error { if o.Name == "" { - return errors.New("missing name") + return missingField(o, "Name") } return nil } @@ -174,27 +146,25 @@ type PlacementGroupCreateResult struct { // Create creates a new PlacementGroup. func (c *PlacementGroupClient) Create(ctx context.Context, opts PlacementGroupCreateOpts) (PlacementGroupCreateResult, *Response, error) { + const opPath = "/placement_groups" + ctx = ctxutil.SetOpPath(ctx, opPath) + + result := PlacementGroupCreateResult{} + + reqPath := opPath + if err := opts.Validate(); err != nil { - return PlacementGroupCreateResult{}, nil, err + return result, nil, err } + reqBody := placementGroupCreateOptsToSchema(opts) - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return PlacementGroupCreateResult{}, nil, err - } - req, err := c.client.NewRequest(ctx, "POST", "/placement_groups", bytes.NewReader(reqBodyData)) - if err != nil { - return PlacementGroupCreateResult{}, nil, err - } - respBody := schema.PlacementGroupCreateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.PlacementGroupCreateResponse](ctx, c.client, reqPath, reqBody) if err != nil { - return PlacementGroupCreateResult{}, nil, err - } - result := PlacementGroupCreateResult{ - PlacementGroup: PlacementGroupFromSchema(respBody.PlacementGroup), + return result, resp, err } + + result.PlacementGroup = PlacementGroupFromSchema(respBody.PlacementGroup) if respBody.Action != nil { result.Action = ActionFromSchema(*respBody.Action) } @@ -210,6 +180,11 @@ type PlacementGroupUpdateOpts struct { // Update updates a PlacementGroup. func (c *PlacementGroupClient) Update(ctx context.Context, placementGroup *PlacementGroup, opts PlacementGroupUpdateOpts) (*PlacementGroup, *Response, error) { + const opPath = "/placement_groups/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, placementGroup.ID) + reqBody := schema.PlacementGroupUpdateRequest{} if opts.Name != "" { reqBody.Name = &opts.Name @@ -217,19 +192,8 @@ func (c *PlacementGroupClient) Update(ctx context.Context, placementGroup *Place if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/placement_groups/%d", placementGroup.ID) - req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.PlacementGroupUpdateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := putRequest[schema.PlacementGroupUpdateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } @@ -239,9 +203,10 @@ func (c *PlacementGroupClient) Update(ctx context.Context, placementGroup *Place // Delete deletes a PlacementGroup. func (c *PlacementGroupClient) Delete(ctx context.Context, placementGroup *PlacementGroup) (*Response, error) { - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/placement_groups/%d", placementGroup.ID), nil) - if err != nil { - return nil, err - } - return c.client.Do(req, nil) + const opPath = "/placement_groups/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, placementGroup.ID) + + return deleteRequestNoResult(ctx, c.client, reqPath) } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/pricing.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/pricing.go index 9e9c4a464ed0ef971668ece3d02b77d73e108563..11ef71ae592abbd765435f9ebb483f4f01b20b92 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/pricing.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/pricing.go @@ -3,15 +3,19 @@ package hcloud import ( "context" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) // Pricing specifies pricing information for various resources. type Pricing struct { - Image ImagePricing - FloatingIP FloatingIPPricing - FloatingIPs []FloatingIPTypePricing - PrimaryIPs []PrimaryIPPricing + Image ImagePricing + // Deprecated: [Pricing.FloatingIP] is deprecated, use [Pricing.FloatingIPs] instead. + FloatingIP FloatingIPPricing + FloatingIPs []FloatingIPTypePricing + PrimaryIPs []PrimaryIPPricing + // Deprecated: [Pricing.Traffic] is deprecated and will report 0 after 2024-08-05. + // Use traffic pricing from [Pricing.ServerTypes] or [Pricing.LoadBalancerTypes] instead. Traffic TrafficPricing ServerBackup ServerBackupPricing ServerTypes []ServerTypePricing @@ -102,6 +106,10 @@ type ServerTypeLocationPricing struct { Location *Location Hourly Price Monthly Price + + // IncludedTraffic is the free traffic per month in bytes + IncludedTraffic uint64 + PerTBTraffic Price } // LoadBalancerTypePricing provides pricing information for a Load Balancer type. @@ -116,6 +124,10 @@ type LoadBalancerTypeLocationPricing struct { Location *Location Hourly Price Monthly Price + + // IncludedTraffic is the free traffic per month in bytes + IncludedTraffic uint64 + PerTBTraffic Price } // PricingClient is a client for the pricing API. @@ -125,15 +137,15 @@ type PricingClient struct { // Get retrieves pricing information. func (c *PricingClient) Get(ctx context.Context) (Pricing, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", "/pricing", nil) - if err != nil { - return Pricing{}, nil, err - } + const opPath = "/pricing" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := opPath - var body schema.PricingGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.PricingGetResponse](ctx, c.client, reqPath) if err != nil { - return Pricing{}, nil, err + return Pricing{}, resp, err } - return PricingFromSchema(body.Pricing), resp, nil + + return PricingFromSchema(respBody.Pricing), resp, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/primary_ip.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/primary_ip.go index 48cc4edde194d97177dd598f00efcffb35b68d72..4ab686e0cdf3a26a961cd7b39a81398a3049c816 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/primary_ip.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/primary_ip.go @@ -1,15 +1,13 @@ package hcloud import ( - "bytes" "context" - "encoding/json" "fmt" "net" "net/url" - "strconv" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -46,26 +44,21 @@ type PrimaryIPDNSPTR struct { // changeDNSPtr changes or resets the reverse DNS pointer for a IP address. // Pass a nil ptr to reset the reverse DNS pointer to its default value. func (p *PrimaryIP) changeDNSPtr(ctx context.Context, client *Client, ip net.IP, ptr *string) (*Action, *Response, error) { + const opPath = "/primary_ips/%d/actions/change_dns_ptr" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, p.ID) + reqBody := schema.PrimaryIPActionChangeDNSPtrRequest{ IP: ip.String(), DNSPtr: ptr, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/primary_ips/%d/actions/change_dns_ptr", p.ID) - req, err := client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - var respBody PrimaryIPChangeDNSPtrResult - resp, err := client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.PrimaryIPActionChangeDNSPtrResponse](ctx, client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -92,13 +85,13 @@ const ( // PrimaryIPCreateOpts defines the request to // create a Primary IP. type PrimaryIPCreateOpts struct { - AssigneeID *int64 `json:"assignee_id,omitempty"` - AssigneeType string `json:"assignee_type"` - AutoDelete *bool `json:"auto_delete,omitempty"` - Datacenter string `json:"datacenter,omitempty"` - Labels map[string]string `json:"labels,omitempty"` - Name string `json:"name"` - Type PrimaryIPType `json:"type"` + AssigneeID *int64 + AssigneeType string + AutoDelete *bool + Datacenter string + Labels map[string]string + Name string + Type PrimaryIPType } // PrimaryIPCreateResult defines the response @@ -111,51 +104,42 @@ type PrimaryIPCreateResult struct { // PrimaryIPUpdateOpts defines the request to // update a Primary IP. type PrimaryIPUpdateOpts struct { - AutoDelete *bool `json:"auto_delete,omitempty"` - Labels *map[string]string `json:"labels,omitempty"` - Name string `json:"name,omitempty"` + AutoDelete *bool + Labels *map[string]string + Name string } // PrimaryIPAssignOpts defines the request to // assign a Primary IP to an assignee (usually a server). type PrimaryIPAssignOpts struct { ID int64 - AssigneeID int64 `json:"assignee_id"` - AssigneeType string `json:"assignee_type"` + AssigneeID int64 + AssigneeType string } -// PrimaryIPAssignResult defines the response -// when assigning a Primary IP to a assignee. -type PrimaryIPAssignResult struct { - Action schema.Action `json:"action"` -} +// Deprecated: Please use [schema.PrimaryIPActionAssignResponse] instead. +type PrimaryIPAssignResult = schema.PrimaryIPActionAssignResponse // PrimaryIPChangeDNSPtrOpts defines the request to // change a DNS PTR entry from a Primary IP. type PrimaryIPChangeDNSPtrOpts struct { ID int64 - DNSPtr string `json:"dns_ptr"` - IP string `json:"ip"` + DNSPtr string + IP string } -// PrimaryIPChangeDNSPtrResult defines the response -// when assigning a Primary IP to a assignee. -type PrimaryIPChangeDNSPtrResult struct { - Action schema.Action `json:"action"` -} +// Deprecated: Please use [schema.PrimaryIPChangeDNSPtrResponse] instead. +type PrimaryIPChangeDNSPtrResult = schema.PrimaryIPActionChangeDNSPtrResponse // PrimaryIPChangeProtectionOpts defines the request to // change protection configuration of a Primary IP. type PrimaryIPChangeProtectionOpts struct { ID int64 - Delete bool `json:"delete"` + Delete bool } -// PrimaryIPChangeProtectionResult defines the response -// when changing a protection of a PrimaryIP. -type PrimaryIPChangeProtectionResult struct { - Action schema.Action `json:"action"` -} +// Deprecated: Please use [schema.PrimaryIPActionChangeProtectionResponse] instead. +type PrimaryIPChangeProtectionResult = schema.PrimaryIPActionChangeProtectionResponse // PrimaryIPClient is a client for the Primary IP API. type PrimaryIPClient struct { @@ -165,20 +149,20 @@ type PrimaryIPClient struct { // GetByID retrieves a Primary IP by its ID. If the Primary IP does not exist, nil is returned. func (c *PrimaryIPClient) GetByID(ctx context.Context, id int64) (*PrimaryIP, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/primary_ips/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/primary_ips/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.PrimaryIPGetResult - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.PrimaryIPGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return PrimaryIPFromSchema(body.PrimaryIP), resp, nil + + return PrimaryIPFromSchema(respBody.PrimaryIP), resp, nil } // GetByIP retrieves a Primary IP by its IP Address. If the Primary IP does not exist, nil is returned. @@ -186,32 +170,22 @@ func (c *PrimaryIPClient) GetByIP(ctx context.Context, ip string) (*PrimaryIP, * if ip == "" { return nil, nil, nil } - primaryIPs, response, err := c.List(ctx, PrimaryIPListOpts{IP: ip}) - if len(primaryIPs) == 0 { - return nil, response, err - } - return primaryIPs[0], response, err + return firstBy(func() ([]*PrimaryIP, *Response, error) { + return c.List(ctx, PrimaryIPListOpts{IP: ip}) + }) } // GetByName retrieves a Primary IP by its name. If the Primary IP does not exist, nil is returned. func (c *PrimaryIPClient) GetByName(ctx context.Context, name string) (*PrimaryIP, *Response, error) { - if name == "" { - return nil, nil, nil - } - primaryIPs, response, err := c.List(ctx, PrimaryIPListOpts{Name: name}) - if len(primaryIPs) == 0 { - return nil, response, err - } - return primaryIPs[0], response, err + return firstByName(name, func() ([]*PrimaryIP, *Response, error) { + return c.List(ctx, PrimaryIPListOpts{Name: name}) + }) } // Get retrieves a Primary IP by its ID if the input can be parsed as an integer, otherwise it // retrieves a Primary IP by its name. If the Primary IP does not exist, nil is returned. func (c *PrimaryIPClient) Get(ctx context.Context, idOrName string) (*PrimaryIP, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByName(ctx, idOrName) + return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName) } // PrimaryIPListOpts specifies options for listing Primary IPs. @@ -241,22 +215,17 @@ func (l PrimaryIPListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *PrimaryIPClient) List(ctx context.Context, opts PrimaryIPListOpts) ([]*PrimaryIP, *Response, error) { - path := "/primary_ips?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/primary_ips?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) - var body schema.PrimaryIPListResult - resp, err := c.client.Do(req, &body) + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) + + respBody, resp, err := getRequest[schema.PrimaryIPListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - primaryIPs := make([]*PrimaryIP, 0, len(body.PrimaryIPs)) - for _, s := range body.PrimaryIPs { - primaryIPs = append(primaryIPs, PrimaryIPFromSchema(s)) + return nil, resp, err } - return primaryIPs, resp, nil + + return allFromSchemaFunc(respBody.PrimaryIPs, PrimaryIPFromSchema), resp, nil } // All returns all Primary IPs. @@ -266,157 +235,125 @@ func (c *PrimaryIPClient) All(ctx context.Context) ([]*PrimaryIP, error) { // AllWithOpts returns all Primary IPs for the given options. func (c *PrimaryIPClient) AllWithOpts(ctx context.Context, opts PrimaryIPListOpts) ([]*PrimaryIP, error) { - allPrimaryIPs := []*PrimaryIP{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*PrimaryIP, *Response, error) { opts.Page = page - primaryIPs, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allPrimaryIPs = append(allPrimaryIPs, primaryIPs...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allPrimaryIPs, nil } // Create creates a Primary IP. -func (c *PrimaryIPClient) Create(ctx context.Context, reqBody PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error) { - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return &PrimaryIPCreateResult{}, nil, err - } +func (c *PrimaryIPClient) Create(ctx context.Context, opts PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error) { + const opPath = "/primary_ips" + ctx = ctxutil.SetOpPath(ctx, opPath) - req, err := c.client.NewRequest(ctx, "POST", "/primary_ips", bytes.NewReader(reqBodyData)) - if err != nil { - return &PrimaryIPCreateResult{}, nil, err - } + result := &PrimaryIPCreateResult{} - var respBody schema.PrimaryIPCreateResponse - resp, err := c.client.Do(req, &respBody) + reqPath := opPath + + reqBody := SchemaFromPrimaryIPCreateOpts(opts) + + respBody, resp, err := postRequest[schema.PrimaryIPCreateResponse](ctx, c.client, reqPath, reqBody) if err != nil { - return &PrimaryIPCreateResult{}, resp, err + return result, resp, err } - var action *Action + + result.PrimaryIP = PrimaryIPFromSchema(respBody.PrimaryIP) if respBody.Action != nil { - action = ActionFromSchema(*respBody.Action) + result.Action = ActionFromSchema(*respBody.Action) } - primaryIP := PrimaryIPFromSchema(respBody.PrimaryIP) - return &PrimaryIPCreateResult{ - PrimaryIP: primaryIP, - Action: action, - }, resp, nil + + return result, resp, nil } // Delete deletes a Primary IP. func (c *PrimaryIPClient) Delete(ctx context.Context, primaryIP *PrimaryIP) (*Response, error) { - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/primary_ips/%d", primaryIP.ID), nil) - if err != nil { - return nil, err - } - return c.client.Do(req, nil) + const opPath = "/primary_ips/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, primaryIP.ID) + + return deleteRequestNoResult(ctx, c.client, reqPath) } // Update updates a Primary IP. -func (c *PrimaryIPClient) Update(ctx context.Context, primaryIP *PrimaryIP, reqBody PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error) { - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } +func (c *PrimaryIPClient) Update(ctx context.Context, primaryIP *PrimaryIP, opts PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error) { + const opPath = "/primary_ips/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) - path := fmt.Sprintf("/primary_ips/%d", primaryIP.ID) - req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } + reqPath := fmt.Sprintf(opPath, primaryIP.ID) + + reqBody := SchemaFromPrimaryIPUpdateOpts(opts) - var respBody schema.PrimaryIPUpdateResult - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := putRequest[schema.PrimaryIPUpdateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return PrimaryIPFromSchema(respBody.PrimaryIP), resp, nil } // Assign a Primary IP to a resource. func (c *PrimaryIPClient) Assign(ctx context.Context, opts PrimaryIPAssignOpts) (*Action, *Response, error) { - reqBodyData, err := json.Marshal(opts) - if err != nil { - return nil, nil, err - } + const opPath = "/primary_ips/%d/actions/assign" + ctx = ctxutil.SetOpPath(ctx, opPath) - path := fmt.Sprintf("/primary_ips/%d/actions/assign", opts.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } + reqPath := fmt.Sprintf(opPath, opts.ID) - var respBody PrimaryIPAssignResult - resp, err := c.client.Do(req, &respBody) + reqBody := SchemaFromPrimaryIPAssignOpts(opts) + + respBody, resp, err := postRequest[schema.PrimaryIPActionAssignResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } // Unassign a Primary IP from a resource. func (c *PrimaryIPClient) Unassign(ctx context.Context, id int64) (*Action, *Response, error) { - path := fmt.Sprintf("/primary_ips/%d/actions/unassign", id) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader([]byte{})) - if err != nil { - return nil, nil, err - } + const opPath = "/primary_ips/%d/actions/unassign" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var respBody PrimaryIPAssignResult - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.PrimaryIPActionUnassignResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } // ChangeDNSPtr Change the reverse DNS from a Primary IP. func (c *PrimaryIPClient) ChangeDNSPtr(ctx context.Context, opts PrimaryIPChangeDNSPtrOpts) (*Action, *Response, error) { - reqBodyData, err := json.Marshal(opts) - if err != nil { - return nil, nil, err - } + const opPath = "/primary_ips/%d/actions/change_dns_ptr" + ctx = ctxutil.SetOpPath(ctx, opPath) - path := fmt.Sprintf("/primary_ips/%d/actions/change_dns_ptr", opts.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } + reqPath := fmt.Sprintf(opPath, opts.ID) + + reqBody := SchemaFromPrimaryIPChangeDNSPtrOpts(opts) - var respBody PrimaryIPChangeDNSPtrResult - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.PrimaryIPActionChangeDNSPtrResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } // ChangeProtection Changes the protection configuration of a Primary IP. func (c *PrimaryIPClient) ChangeProtection(ctx context.Context, opts PrimaryIPChangeProtectionOpts) (*Action, *Response, error) { - reqBodyData, err := json.Marshal(opts) - if err != nil { - return nil, nil, err - } + const opPath = "/primary_ips/%d/actions/change_protection" + ctx = ctxutil.SetOpPath(ctx, opPath) - path := fmt.Sprintf("/primary_ips/%d/actions/change_protection", opts.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } + reqPath := fmt.Sprintf(opPath, opts.ID) - var respBody PrimaryIPChangeProtectionResult - resp, err := c.client.Do(req, &respBody) + reqBody := SchemaFromPrimaryIPChangeProtectionOpts(opts) + + respBody, resp, err := postRequest[schema.PrimaryIPActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema.go index 1f5ae42211d721f4d70a098dcb510d14b116f837..fd9d08ee2914d71f7a2384ade792d7c763cfed58 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema.go @@ -49,6 +49,26 @@ func SchemaFromPrimaryIP(p *PrimaryIP) schema.PrimaryIP { return c.SchemaFromPrimaryIP(p) } +func SchemaFromPrimaryIPCreateOpts(o PrimaryIPCreateOpts) schema.PrimaryIPCreateRequest { + return c.SchemaFromPrimaryIPCreateOpts(o) +} + +func SchemaFromPrimaryIPUpdateOpts(o PrimaryIPUpdateOpts) schema.PrimaryIPUpdateRequest { + return c.SchemaFromPrimaryIPUpdateOpts(o) +} + +func SchemaFromPrimaryIPChangeDNSPtrOpts(o PrimaryIPChangeDNSPtrOpts) schema.PrimaryIPActionChangeDNSPtrRequest { + return c.SchemaFromPrimaryIPChangeDNSPtrOpts(o) +} + +func SchemaFromPrimaryIPChangeProtectionOpts(o PrimaryIPChangeProtectionOpts) schema.PrimaryIPActionChangeProtectionRequest { + return c.SchemaFromPrimaryIPChangeProtectionOpts(o) +} + +func SchemaFromPrimaryIPAssignOpts(o PrimaryIPAssignOpts) schema.PrimaryIPActionAssignRequest { + return c.SchemaFromPrimaryIPAssignOpts(o) +} + // ISOFromSchema converts a schema.ISO to an ISO. func ISOFromSchema(s schema.ISO) *ISO { return c.ISOFromSchema(s) diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/doc.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/doc.go new file mode 100644 index 0000000000000000000000000000000000000000..057aef057f05049622debaddff5192d570d4cb36 --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/doc.go @@ -0,0 +1,4 @@ +// The schema package holds API schemas for the `hcloud-go` library. + +// Breaking changes may occur without notice. Do not use in production! +package schema diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/error.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/error.go index 2d5cf5ddd8336090c2c4ae752c2d98ca471b8207..f1066bdee67e3f3920606a8d1f42ab1cda0d14b5 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/error.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/error.go @@ -7,7 +7,7 @@ type Error struct { Code string `json:"code"` Message string `json:"message"` DetailsRaw json.RawMessage `json:"details"` - Details interface{} + Details any `json:"-"` } // UnmarshalJSON overrides default json unmarshalling. @@ -17,13 +17,20 @@ func (e *Error) UnmarshalJSON(data []byte) (err error) { if err = json.Unmarshal(data, alias); err != nil { return } - if e.Code == "invalid_input" { + if e.Code == "invalid_input" && len(e.DetailsRaw) > 0 { details := ErrorDetailsInvalidInput{} if err = json.Unmarshal(e.DetailsRaw, &details); err != nil { return } alias.Details = details } + if e.Code == "deprecated_api_endpoint" && len(e.DetailsRaw) > 0 { + details := ErrorDetailsDeprecatedAPIEndpoint{} + if err = json.Unmarshal(e.DetailsRaw, &details); err != nil { + return + } + alias.Details = details + } return } @@ -40,3 +47,9 @@ type ErrorDetailsInvalidInput struct { Messages []string `json:"messages"` } `json:"fields"` } + +// ErrorDetailsDeprecatedAPIEndpoint defines the schema of the Details field +// of an error with code 'deprecated_api_endpoint'. +type ErrorDetailsDeprecatedAPIEndpoint struct { + Announcement string `json:"announcement"` +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/id_or_name.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/id_or_name.go new file mode 100644 index 0000000000000000000000000000000000000000..75e1169bd47ac28eecd317786e0fb6a976c2dfcc --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/id_or_name.go @@ -0,0 +1,68 @@ +package schema + +import ( + "bytes" + "encoding/json" + "reflect" + "strconv" +) + +// IDOrName can be used in API requests where either a resource id or name can be +// specified. +type IDOrName struct { + ID int64 + Name string +} + +var _ json.Unmarshaler = (*IDOrName)(nil) +var _ json.Marshaler = (*IDOrName)(nil) + +func (o IDOrName) MarshalJSON() ([]byte, error) { + if o.ID != 0 { + return json.Marshal(o.ID) + } + if o.Name != "" { + return json.Marshal(o.Name) + } + + // We want to preserve the behavior of an empty interface{} to prevent breaking + // changes (marshaled to null when empty). + return json.Marshal(nil) +} + +func (o *IDOrName) UnmarshalJSON(data []byte) error { + d := json.NewDecoder(bytes.NewBuffer(data)) + // This ensures we won't lose precision on large IDs, see json.Number below + d.UseNumber() + + var v any + if err := d.Decode(&v); err != nil { + return err + } + + switch typed := v.(type) { + case string: + id, err := strconv.ParseInt(typed, 10, 64) + if err == nil { + o.ID = id + } else if typed != "" { + o.Name = typed + } + case json.Number: + id, err := typed.Int64() + if err != nil { + return &json.UnmarshalTypeError{ + Value: string(data), + Type: reflect.TypeOf(*o), + } + } + o.ID = id + default: + return &json.UnmarshalTypeError{ + Value: string(data), + Type: reflect.TypeOf(*o), + } + } + + return nil +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/load_balancer.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/load_balancer.go index 7e1c4f5da64b572f88f525db4e28c188b4f64391..d8760ad2633ad03d218c0c843bdb89e3193f1b25 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/load_balancer.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/load_balancer.go @@ -251,7 +251,7 @@ type LoadBalancerDeleteServiceResponse struct { type LoadBalancerCreateRequest struct { Name string `json:"name"` - LoadBalancerType interface{} `json:"load_balancer_type"` // int or string + LoadBalancerType IDOrName `json:"load_balancer_type"` Algorithm *LoadBalancerCreateRequestAlgorithm `json:"algorithm,omitempty"` Location *string `json:"location,omitempty"` NetworkZone *string `json:"network_zone,omitempty"` @@ -380,7 +380,7 @@ type LoadBalancerActionDisablePublicInterfaceResponse struct { } type LoadBalancerActionChangeTypeRequest struct { - LoadBalancerType interface{} `json:"load_balancer_type"` // int or string + LoadBalancerType IDOrName `json:"load_balancer_type"` } type LoadBalancerActionChangeTypeResponse struct { diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/network.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/network.go index 2344aea450a6b5c37650a24f4938cb1ec4fd9330..0e89c70eda8a47f8594d51ac8d63ecbebb4026bc 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/network.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/network.go @@ -11,6 +11,7 @@ type Network struct { Subnets []NetworkSubnet `json:"subnets"` Routes []NetworkRoute `json:"routes"` Servers []int64 `json:"servers"` + LoadBalancers []int64 `json:"load_balancers"` Protection NetworkProtection `json:"protection"` Labels map[string]string `json:"labels"` ExposeRoutesToVSwitch bool `json:"expose_routes_to_vswitch"` diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/pricing.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/pricing.go index 192352f5d79058920bb5128aa4e499823b0d99bb..c1adbb1bd762edcd2b2cb92226b3128aeb1ad901 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/pricing.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/pricing.go @@ -2,12 +2,15 @@ package schema // Pricing defines the schema for pricing information. type Pricing struct { - Currency string `json:"currency"` - VATRate string `json:"vat_rate"` - Image PricingImage `json:"image"` - FloatingIP PricingFloatingIP `json:"floating_ip"` - FloatingIPs []PricingFloatingIPType `json:"floating_ips"` - PrimaryIPs []PricingPrimaryIP `json:"primary_ips"` + Currency string `json:"currency"` + VATRate string `json:"vat_rate"` + Image PricingImage `json:"image"` + // Deprecated: [Pricing.FloatingIP] is deprecated, use [Pricing.FloatingIPs] instead. + FloatingIP PricingFloatingIP `json:"floating_ip"` + FloatingIPs []PricingFloatingIPType `json:"floating_ips"` + PrimaryIPs []PricingPrimaryIP `json:"primary_ips"` + // Deprecated: [Pricing.Traffic] is deprecated and will report 0 after 2024-08-05. + // Use traffic pricing from [Pricing.ServerTypes] or [Pricing.LoadBalancerTypes] instead. Traffic PricingTraffic `json:"traffic"` ServerBackup PricingServerBackup `json:"server_backup"` ServerTypes []PricingServerType `json:"server_types"` @@ -72,6 +75,9 @@ type PricingServerTypePrice struct { Location string `json:"location"` PriceHourly Price `json:"price_hourly"` PriceMonthly Price `json:"price_monthly"` + + IncludedTraffic uint64 `json:"included_traffic"` + PricePerTBTraffic Price `json:"price_per_tb_traffic"` } // PricingLoadBalancerType defines the schema of pricing information for a Load Balancer type. @@ -87,6 +93,9 @@ type PricingLoadBalancerTypePrice struct { Location string `json:"location"` PriceHourly Price `json:"price_hourly"` PriceMonthly Price `json:"price_monthly"` + + IncludedTraffic uint64 `json:"included_traffic"` + PricePerTBTraffic Price `json:"price_per_tb_traffic"` } // PricingGetResponse defines the schema of the response when retrieving pricing information. diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/primary_ip.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/primary_ip.go index 1901ce80017024b95bc468c7b4ed1d71fcabfec8..300711fc254e87a5416a2df0497429334e58c73c 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/primary_ip.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/primary_ip.go @@ -31,6 +31,18 @@ type PrimaryIPDNSPTR struct { IP string `json:"ip"` } +// PrimaryIPCreateOpts defines the request to +// create a Primary IP. +type PrimaryIPCreateRequest struct { + Name string `json:"name"` + Type string `json:"type"` + AssigneeType string `json:"assignee_type"` + AssigneeID *int64 `json:"assignee_id,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + AutoDelete *bool `json:"auto_delete,omitempty"` + Datacenter string `json:"datacenter,omitempty"` +} + // PrimaryIPCreateResponse defines the schema of the response // when creating a Primary IP. type PrimaryIPCreateResponse struct { @@ -38,19 +50,27 @@ type PrimaryIPCreateResponse struct { Action *Action `json:"action"` } -// PrimaryIPGetResult defines the response when retrieving a single Primary IP. -type PrimaryIPGetResult struct { +// PrimaryIPGetResponse defines the response when retrieving a single Primary IP. +type PrimaryIPGetResponse struct { PrimaryIP PrimaryIP `json:"primary_ip"` } -// PrimaryIPListResult defines the response when listing Primary IPs. -type PrimaryIPListResult struct { +// PrimaryIPListResponse defines the response when listing Primary IPs. +type PrimaryIPListResponse struct { PrimaryIPs []PrimaryIP `json:"primary_ips"` } -// PrimaryIPUpdateResult defines the response +// PrimaryIPUpdateOpts defines the request to +// update a Primary IP. +type PrimaryIPUpdateRequest struct { + Name string `json:"name,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + AutoDelete *bool `json:"auto_delete,omitempty"` +} + +// PrimaryIPUpdateResponse defines the response // when updating a Primary IP. -type PrimaryIPUpdateResult struct { +type PrimaryIPUpdateResponse struct { PrimaryIP PrimaryIP `json:"primary_ip"` } @@ -60,3 +80,39 @@ type PrimaryIPActionChangeDNSPtrRequest struct { IP string `json:"ip"` DNSPtr *string `json:"dns_ptr"` } + +// PrimaryIPActionChangeDNSPtrResponse defines the response when setting a reverse DNS +// pointer for a IP address. +type PrimaryIPActionChangeDNSPtrResponse struct { + Action Action `json:"action"` +} + +// PrimaryIPActionAssignRequest defines the request to +// assign a Primary IP to an assignee (usually a server). +type PrimaryIPActionAssignRequest struct { + AssigneeID int64 `json:"assignee_id"` + AssigneeType string `json:"assignee_type"` +} + +// PrimaryIPActionAssignResponse defines the response when assigning a Primary IP to a +// assignee. +type PrimaryIPActionAssignResponse struct { + Action Action `json:"action"` +} + +// PrimaryIPActionUnassignResponse defines the response to unassign a Primary IP. +type PrimaryIPActionUnassignResponse struct { + Action Action `json:"action"` +} + +// PrimaryIPActionChangeProtectionRequest defines the request to +// change protection configuration of a Primary IP. +type PrimaryIPActionChangeProtectionRequest struct { + Delete bool `json:"delete"` +} + +// PrimaryIPActionChangeProtectionResponse defines the response when changing the +// protection of a Primary IP. +type PrimaryIPActionChangeProtectionResponse struct { + Action Action `json:"action"` +} diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server.go index 068b3e81f9454d59c213b9397be0333b1110add2..72aaf269de7bd8e6ee0fbb0e899c558b9d0cc477 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server.go @@ -99,8 +99,8 @@ type ServerListResponse struct { // create a server. type ServerCreateRequest struct { Name string `json:"name"` - ServerType interface{} `json:"server_type"` // int or string - Image interface{} `json:"image"` // int or string + ServerType IDOrName `json:"server_type"` + Image IDOrName `json:"image"` SSHKeys []int64 `json:"ssh_keys,omitempty"` Location string `json:"location,omitempty"` Datacenter string `json:"datacenter,omitempty"` @@ -257,7 +257,7 @@ type ServerActionDisableRescueResponse struct { // ServerActionRebuildRequest defines the schema for the request to // rebuild a server. type ServerActionRebuildRequest struct { - Image interface{} `json:"image"` // int or string + Image IDOrName `json:"image"` } // ServerActionRebuildResponse defines the schema of the response when @@ -270,7 +270,7 @@ type ServerActionRebuildResponse struct { // ServerActionAttachISORequest defines the schema for the request to // attach an ISO to a server. type ServerActionAttachISORequest struct { - ISO interface{} `json:"iso"` // int or string + ISO IDOrName `json:"iso"` } // ServerActionAttachISOResponse defines the schema of the response when @@ -289,12 +289,6 @@ type ServerActionDetachISOResponse struct { Action Action `json:"action"` } -// ServerActionEnableBackupRequest defines the schema for the request to -// enable backup for a server. -type ServerActionEnableBackupRequest struct { - BackupWindow *string `json:"backup_window,omitempty"` -} - // ServerActionEnableBackupResponse defines the schema of the response when // creating a enable_backup server action. type ServerActionEnableBackupResponse struct { @@ -314,8 +308,8 @@ type ServerActionDisableBackupResponse struct { // ServerActionChangeTypeRequest defines the schema for the request to // change a server's type. type ServerActionChangeTypeRequest struct { - ServerType interface{} `json:"server_type"` // int or string - UpgradeDisk bool `json:"upgrade_disk"` + ServerType IDOrName `json:"server_type"` + UpgradeDisk bool `json:"upgrade_disk"` } // ServerActionChangeTypeResponse defines the schema of the response when diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server_type.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server_type.go index 21a87da9a9b7cc3cdd9cb314de98e519eeff1d22..60f43a1bf860f40e3962d57351d9b09335c3ca05 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server_type.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server_type.go @@ -2,15 +2,18 @@ package schema // ServerType defines the schema of a server type. type ServerType struct { - ID int64 `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Cores int `json:"cores"` - Memory float32 `json:"memory"` - Disk int `json:"disk"` - StorageType string `json:"storage_type"` - CPUType string `json:"cpu_type"` - Architecture string `json:"architecture"` + ID int64 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Cores int `json:"cores"` + Memory float32 `json:"memory"` + Disk int `json:"disk"` + StorageType string `json:"storage_type"` + CPUType string `json:"cpu_type"` + Architecture string `json:"architecture"` + + // Deprecated: [ServerType.IncludedTraffic] is deprecated and will always report 0 after 2024-08-05. + // Use [ServerType.Prices] instead to get the included traffic for each location. IncludedTraffic int64 `json:"included_traffic"` Prices []PricingServerTypePrice `json:"prices"` Deprecated bool `json:"deprecated"` diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/volume.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/volume.go index 1de6459550c210531d74b0ba5e455016e165f5b1..89223ba43afe74e873e4e57931b082e94dfd5305 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/volume.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/volume.go @@ -23,7 +23,7 @@ type VolumeCreateRequest struct { Name string `json:"name"` Size int `json:"size"` Server *int64 `json:"server,omitempty"` - Location interface{} `json:"location,omitempty"` // int, string, or nil + Location *IDOrName `json:"location,omitempty"` Labels *map[string]string `json:"labels,omitempty"` Automount *bool `json:"automount,omitempty"` Format *string `json:"format,omitempty"` diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema_gen.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema_gen.go index 7cc9df8d052ea26c4414bd1c42b2deb3b2352a47..b50df5ef277f41c2b39393af1141fadb1f3bebb9 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema_gen.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema_gen.go @@ -69,7 +69,6 @@ You can find a documentation of goverter here: https://goverter.jmattheis.de/ // goverter:extend durationFromIntSeconds // goverter:extend intSecondsFromDuration // goverter:extend serverFromImageCreatedFromSchema -// goverter:extend anyFromLoadBalancerType // goverter:extend serverMetricsTimeSeriesFromSchema // goverter:extend loadBalancerMetricsTimeSeriesFromSchema // goverter:extend stringPtrFromLoadBalancerServiceProtocol @@ -108,6 +107,12 @@ type converter interface { // goverter:map AssigneeID | mapZeroInt64ToNil SchemaFromPrimaryIP(*PrimaryIP) schema.PrimaryIP + SchemaFromPrimaryIPCreateOpts(PrimaryIPCreateOpts) schema.PrimaryIPCreateRequest + SchemaFromPrimaryIPUpdateOpts(PrimaryIPUpdateOpts) schema.PrimaryIPUpdateRequest + SchemaFromPrimaryIPChangeDNSPtrOpts(PrimaryIPChangeDNSPtrOpts) schema.PrimaryIPActionChangeDNSPtrRequest + SchemaFromPrimaryIPChangeProtectionOpts(PrimaryIPChangeProtectionOpts) schema.PrimaryIPActionChangeProtectionRequest + SchemaFromPrimaryIPAssignOpts(PrimaryIPAssignOpts) schema.PrimaryIPActionAssignRequest + ISOFromSchema(schema.ISO) *ISO // We cannot use goverter settings when mapping a struct to a struct pointer @@ -207,10 +212,12 @@ type converter interface { // goverter:map PriceHourly Hourly // goverter:map PriceMonthly Monthly + // goverter:map PricePerTBTraffic PerTBTraffic LoadBalancerTypeLocationPricingFromSchema(schema.PricingLoadBalancerTypePrice) LoadBalancerTypeLocationPricing // goverter:map Hourly PriceHourly // goverter:map Monthly PriceMonthly + // goverter:map PerTBTraffic PricePerTBTraffic SchemaFromLoadBalancerTypeLocationPricing(LoadBalancerTypeLocationPricing) schema.PricingLoadBalancerTypePrice LoadBalancerServiceFromSchema(schema.LoadBalancerService) LoadBalancerService @@ -263,6 +270,7 @@ type converter interface { // goverter:map PriceHourly Hourly // goverter:map PriceMonthly Monthly + // goverter:map PricePerTBTraffic PerTBTraffic serverTypePricingFromSchema(schema.PricingServerTypePrice) ServerTypeLocationPricing // goverter:map Image.PerGBMonth.Currency Currency @@ -306,6 +314,7 @@ type converter interface { // goverter:map Monthly PriceMonthly // goverter:map Hourly PriceHourly + // goverter:map PerTBTraffic PricePerTBTraffic schemaFromServerTypeLocationPricing(ServerTypeLocationPricing) schema.PricingServerTypePrice FirewallFromSchema(schema.Firewall) *Firewall @@ -606,37 +615,48 @@ func intSecondsFromDuration(d time.Duration) int { } func errorDetailsFromSchema(d interface{}) interface{} { - if d, ok := d.(schema.ErrorDetailsInvalidInput); ok { + switch typed := d.(type) { + case schema.ErrorDetailsInvalidInput: details := ErrorDetailsInvalidInput{ - Fields: make([]ErrorDetailsInvalidInputField, len(d.Fields)), + Fields: make([]ErrorDetailsInvalidInputField, len(typed.Fields)), } - for i, field := range d.Fields { + for i, field := range typed.Fields { details.Fields[i] = ErrorDetailsInvalidInputField{ Name: field.Name, Messages: field.Messages, } } return details + + case schema.ErrorDetailsDeprecatedAPIEndpoint: + return ErrorDetailsDeprecatedAPIEndpoint{ + Announcement: typed.Announcement, + } } return nil } func schemaFromErrorDetails(d interface{}) interface{} { - if d, ok := d.(ErrorDetailsInvalidInput); ok { + switch typed := d.(type) { + case ErrorDetailsInvalidInput: details := schema.ErrorDetailsInvalidInput{ Fields: make([]struct { Name string `json:"name"` Messages []string `json:"messages"` - }, len(d.Fields)), + }, len(typed.Fields)), } - for i, field := range d.Fields { + for i, field := range typed.Fields { details.Fields[i] = struct { Name string `json:"name"` Messages []string `json:"messages"` }{Name: field.Name, Messages: field.Messages} } return details + + case ErrorDetailsDeprecatedAPIEndpoint: + return schema.ErrorDetailsDeprecatedAPIEndpoint{Announcement: typed.Announcement} } + return nil } @@ -654,8 +674,8 @@ func imagePricingFromSchema(s schema.Pricing) ImagePricing { func floatingIPPricingFromSchema(s schema.Pricing) FloatingIPPricing { return FloatingIPPricing{ Monthly: Price{ - Net: s.FloatingIP.PriceMonthly.Net, - Gross: s.FloatingIP.PriceMonthly.Gross, + Net: s.FloatingIP.PriceMonthly.Net, // nolint:staticcheck // Field is deprecated, but removal is not planned + Gross: s.FloatingIP.PriceMonthly.Gross, // nolint:staticcheck // Field is deprecated, but removal is not planned Currency: s.Currency, VATRate: s.VATRate, }, @@ -707,8 +727,8 @@ func primaryIPPricingFromSchema(s schema.Pricing) []PrimaryIPPricing { func trafficPricingFromSchema(s schema.Pricing) TrafficPricing { return TrafficPricing{ PerTB: Price{ - Net: s.Traffic.PricePerTB.Net, - Gross: s.Traffic.PricePerTB.Gross, + Net: s.Traffic.PricePerTB.Net, // nolint:staticcheck // Field is deprecated, but we still need to map it as long as it is available + Gross: s.Traffic.PricePerTB.Gross, // nolint:staticcheck // Field is deprecated, but we still need to map it as long as it is available Currency: s.Currency, VATRate: s.VATRate, }, @@ -734,6 +754,13 @@ func serverTypePricingFromSchema(s schema.Pricing) []ServerTypePricing { Net: price.PriceMonthly.Net, Gross: price.PriceMonthly.Gross, }, + IncludedTraffic: price.IncludedTraffic, + PerTBTraffic: Price{ + Currency: s.Currency, + VATRate: s.VATRate, + Net: price.PricePerTBTraffic.Net, + Gross: price.PricePerTBTraffic.Gross, + }, } } p[i] = ServerTypePricing{ @@ -766,6 +793,13 @@ func loadBalancerTypePricingFromSchema(s schema.Pricing) []LoadBalancerTypePrici Net: price.PriceMonthly.Net, Gross: price.PriceMonthly.Gross, }, + IncludedTraffic: price.IncludedTraffic, + PerTBTraffic: Price{ + Currency: s.Currency, + VATRate: s.VATRate, + Net: price.PricePerTBTraffic.Net, + Gross: price.PricePerTBTraffic.Gross, + }, } } p[i] = LoadBalancerTypePricing{ @@ -790,16 +824,6 @@ func volumePricingFromSchema(s schema.Pricing) VolumePricing { } } -func anyFromLoadBalancerType(t *LoadBalancerType) interface{} { - if t == nil { - return nil - } - if t.ID != 0 { - return t.ID - } - return t.Name -} - func serverMetricsTimeSeriesFromSchema(s schema.ServerTimeSeriesVals) ([]ServerMetricsValue, error) { vals := make([]ServerMetricsValue, len(s.Values)) @@ -922,7 +946,10 @@ func rawSchemaFromErrorDetails(v interface{}) json.RawMessage { if v == nil { return nil } - msg, _ := json.Marshal(d) + msg, err := json.Marshal(d) + if err != nil { + return nil + } return msg } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server.go index 8898456281ec947469086bd488a65d1467c1b328..78b822a40745969aafaa9193eb8aa11572ce8bdf 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server.go @@ -1,17 +1,14 @@ package hcloud import ( - "bytes" "context" - "encoding/json" - "errors" "fmt" "net" - "net/http" "net/url" "strconv" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -155,26 +152,21 @@ const ( // changeDNSPtr changes or resets the reverse DNS pointer for a IP address. // Pass a nil ptr to reset the reverse DNS pointer to its default value. func (s *Server) changeDNSPtr(ctx context.Context, client *Client, ip net.IP, ptr *string) (*Action, *Response, error) { + const opPath = "/servers/%d/actions/change_dns_ptr" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, s.ID) + reqBody := schema.ServerActionChangeDNSPtrRequest{ IP: ip.String(), DNSPtr: ptr, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/servers/%d/actions/change_dns_ptr", s.ID) - req, err := client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.ServerActionChangeDNSPtrResponse{} - resp, err := client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionChangeDNSPtrResponse](ctx, client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -198,41 +190,33 @@ type ServerClient struct { // GetByID retrieves a server by its ID. If the server does not exist, nil is returned. func (c *ServerClient) GetByID(ctx context.Context, id int64) (*Server, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/servers/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/servers/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.ServerGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.ServerGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return ServerFromSchema(body.Server), resp, nil + + return ServerFromSchema(respBody.Server), resp, nil } // GetByName retrieves a server by its name. If the server does not exist, nil is returned. func (c *ServerClient) GetByName(ctx context.Context, name string) (*Server, *Response, error) { - if name == "" { - return nil, nil, nil - } - servers, response, err := c.List(ctx, ServerListOpts{Name: name}) - if len(servers) == 0 { - return nil, response, err - } - return servers[0], response, err + return firstByName(name, func() ([]*Server, *Response, error) { + return c.List(ctx, ServerListOpts{Name: name}) + }) } // Get retrieves a server by its ID if the input can be parsed as an integer, otherwise it // retrieves a server by its name. If the server does not exist, nil is returned. func (c *ServerClient) Get(ctx context.Context, idOrName string) (*Server, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByName(ctx, idOrName) + return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName) } // ServerListOpts specifies options for listing servers. @@ -262,22 +246,17 @@ func (l ServerListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *ServerClient) List(ctx context.Context, opts ServerListOpts) ([]*Server, *Response, error) { - path := "/servers?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/servers?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) - var body schema.ServerListResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.ServerListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - servers := make([]*Server, 0, len(body.Servers)) - for _, s := range body.Servers { - servers = append(servers, ServerFromSchema(s)) + return nil, resp, err } - return servers, resp, nil + + return allFromSchemaFunc(respBody.Servers, ServerFromSchema), resp, nil } // All returns all servers. @@ -287,22 +266,10 @@ func (c *ServerClient) All(ctx context.Context) ([]*Server, error) { // AllWithOpts returns all servers for the given options. func (c *ServerClient) AllWithOpts(ctx context.Context, opts ServerListOpts) ([]*Server, error) { - allServers := []*Server{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*Server, *Response, error) { opts.Page = page - servers, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allServers = append(allServers, servers...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allServers, nil } // ServerCreateOpts specifies options for creating a new server. @@ -339,22 +306,16 @@ type ServerCreateFirewall struct { // Validate checks if options are valid. func (o ServerCreateOpts) Validate() error { if o.Name == "" { - return errors.New("missing name") + return missingField(o, "Name") } if o.ServerType == nil || (o.ServerType.ID == 0 && o.ServerType.Name == "") { - return errors.New("missing server type") + return missingField(o, "ServerType") } if o.Image == nil || (o.Image.ID == 0 && o.Image.Name == "") { - return errors.New("missing image") + return missingField(o, "Image") } if o.Location != nil && o.Datacenter != nil { - return errors.New("location and datacenter are mutually exclusive") - } - if o.PublicNet != nil { - if !o.PublicNet.EnableIPv4 && !o.PublicNet.EnableIPv6 && - len(o.Networks) == 0 && (o.StartAfterCreate == nil || *o.StartAfterCreate) { - return errors.New("missing networks or StartAfterCreate == false when EnableIPv4 and EnableIPv6 is false") - } + return mutuallyExclusiveFields(o, "Location", "Datacenter") } return nil } @@ -369,8 +330,15 @@ type ServerCreateResult struct { // Create creates a new server. func (c *ServerClient) Create(ctx context.Context, opts ServerCreateOpts) (ServerCreateResult, *Response, error) { + const opPath = "/servers" + ctx = ctxutil.SetOpPath(ctx, opPath) + + result := ServerCreateResult{} + + reqPath := opPath + if err := opts.Validate(); err != nil { - return ServerCreateResult{}, nil, err + return result, nil, err } var reqBody schema.ServerCreateRequest @@ -378,15 +346,11 @@ func (c *ServerClient) Create(ctx context.Context, opts ServerCreateOpts) (Serve reqBody.Name = opts.Name reqBody.Automount = opts.Automount reqBody.StartAfterCreate = opts.StartAfterCreate - if opts.ServerType.ID != 0 { - reqBody.ServerType = opts.ServerType.ID - } else if opts.ServerType.Name != "" { - reqBody.ServerType = opts.ServerType.Name + if opts.ServerType.ID != 0 || opts.ServerType.Name != "" { + reqBody.ServerType = schema.IDOrName{ID: opts.ServerType.ID, Name: opts.ServerType.Name} } - if opts.Image.ID != 0 { - reqBody.Image = opts.Image.ID - } else if opts.Image.Name != "" { - reqBody.Image = opts.Image.Name + if opts.Image.ID != 0 || opts.Image.Name != "" { + reqBody.Image = schema.IDOrName{ID: opts.Image.ID, Name: opts.Image.Name} } if opts.Labels != nil { reqBody.Labels = &opts.Labels @@ -435,29 +399,19 @@ func (c *ServerClient) Create(ctx context.Context, opts ServerCreateOpts) (Serve if opts.PlacementGroup != nil { reqBody.PlacementGroup = opts.PlacementGroup.ID } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return ServerCreateResult{}, nil, err - } - req, err := c.client.NewRequest(ctx, "POST", "/servers", bytes.NewReader(reqBodyData)) + respBody, resp, err := postRequest[schema.ServerCreateResponse](ctx, c.client, reqPath, reqBody) if err != nil { - return ServerCreateResult{}, nil, err + return result, resp, err } - var respBody schema.ServerCreateResponse - resp, err := c.client.Do(req, &respBody) - if err != nil { - return ServerCreateResult{}, resp, err - } - result := ServerCreateResult{ - Server: ServerFromSchema(respBody.Server), - Action: ActionFromSchema(respBody.Action), - NextActions: ActionsFromSchema(respBody.NextActions), - } + result.Server = ServerFromSchema(respBody.Server) + result.Action = ActionFromSchema(respBody.Action) + result.NextActions = ActionsFromSchema(respBody.NextActions) if respBody.RootPassword != nil { result.RootPassword = *respBody.RootPassword } + return result, resp, nil } @@ -476,20 +430,21 @@ func (c *ServerClient) Delete(ctx context.Context, server *Server) (*Response, e // DeleteWithResult deletes a server and returns the parsed response containing the action. func (c *ServerClient) DeleteWithResult(ctx context.Context, server *Server) (*ServerDeleteResult, *Response, error) { - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/servers/%d", server.ID), nil) - if err != nil { - return &ServerDeleteResult{}, nil, err - } + const opPath = "/servers/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) - var respBody schema.ServerDeleteResponse - resp, err := c.client.Do(req, &respBody) + result := &ServerDeleteResult{} + + reqPath := fmt.Sprintf(opPath, server.ID) + + respBody, resp, err := deleteRequest[schema.ServerDeleteResponse](ctx, c.client, reqPath) if err != nil { - return &ServerDeleteResult{}, resp, err + return result, resp, err } - return &ServerDeleteResult{ - Action: ActionFromSchema(respBody.Action), - }, resp, nil + result.Action = ActionFromSchema(respBody.Action) + + return result, resp, nil } // ServerUpdateOpts specifies options for updating a server. @@ -500,108 +455,98 @@ type ServerUpdateOpts struct { // Update updates a server. func (c *ServerClient) Update(ctx context.Context, server *Server, opts ServerUpdateOpts) (*Server, *Response, error) { + const opPath = "/servers/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) + reqBody := schema.ServerUpdateRequest{ Name: opts.Name, } if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/servers/%d", server.ID) - req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.ServerUpdateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := putRequest[schema.ServerUpdateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ServerFromSchema(respBody.Server), resp, nil } // Poweron starts a server. func (c *ServerClient) Poweron(ctx context.Context, server *Server) (*Action, *Response, error) { - path := fmt.Sprintf("/servers/%d/actions/poweron", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/servers/%d/actions/poweron" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) - respBody := schema.ServerActionPoweronResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionPoweronResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } // Reboot reboots a server. func (c *ServerClient) Reboot(ctx context.Context, server *Server) (*Action, *Response, error) { - path := fmt.Sprintf("/servers/%d/actions/reboot", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/servers/%d/actions/reboot" + ctx = ctxutil.SetOpPath(ctx, opPath) - respBody := schema.ServerActionRebootResponse{} - resp, err := c.client.Do(req, &respBody) + reqPath := fmt.Sprintf(opPath, server.ID) + + respBody, resp, err := postRequest[schema.ServerActionRebootResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } // Reset resets a server. func (c *ServerClient) Reset(ctx context.Context, server *Server) (*Action, *Response, error) { - path := fmt.Sprintf("/servers/%d/actions/reset", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/servers/%d/actions/reset" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) - respBody := schema.ServerActionResetResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionResetResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } // Shutdown shuts down a server. func (c *ServerClient) Shutdown(ctx context.Context, server *Server) (*Action, *Response, error) { - path := fmt.Sprintf("/servers/%d/actions/shutdown", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/servers/%d/actions/shutdown" + ctx = ctxutil.SetOpPath(ctx, opPath) - respBody := schema.ServerActionShutdownResponse{} - resp, err := c.client.Do(req, &respBody) + reqPath := fmt.Sprintf(opPath, server.ID) + + respBody, resp, err := postRequest[schema.ServerActionShutdownResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } // Poweroff stops a server. func (c *ServerClient) Poweroff(ctx context.Context, server *Server) (*Action, *Response, error) { - path := fmt.Sprintf("/servers/%d/actions/poweroff", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/servers/%d/actions/poweroff" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) - respBody := schema.ServerActionPoweroffResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionPoweroffResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -613,21 +558,22 @@ type ServerResetPasswordResult struct { // ResetPassword resets a server's password. func (c *ServerClient) ResetPassword(ctx context.Context, server *Server) (ServerResetPasswordResult, *Response, error) { - path := fmt.Sprintf("/servers/%d/actions/reset_password", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return ServerResetPasswordResult{}, nil, err - } + const opPath = "/servers/%d/actions/reset_password" + ctx = ctxutil.SetOpPath(ctx, opPath) + + result := ServerResetPasswordResult{} - respBody := schema.ServerActionResetPasswordResponse{} - resp, err := c.client.Do(req, &respBody) + reqPath := fmt.Sprintf(opPath, server.ID) + + respBody, resp, err := postRequest[schema.ServerActionResetPasswordResponse](ctx, c.client, reqPath, nil) if err != nil { - return ServerResetPasswordResult{}, resp, err + return result, resp, err } - return ServerResetPasswordResult{ - Action: ActionFromSchema(respBody.Action), - RootPassword: respBody.RootPassword, - }, resp, nil + + result.Action = ActionFromSchema(respBody.Action) + result.RootPassword = respBody.RootPassword + + return result, resp, nil } // ServerCreateImageOpts specifies options for creating an image from a server. @@ -645,7 +591,7 @@ func (o ServerCreateImageOpts) Validate() error { case "": break default: - return errors.New("invalid type") + return invalidFieldValue(o, "Type", o.Type) } return nil @@ -659,10 +605,17 @@ type ServerCreateImageResult struct { // CreateImage creates an image from a server. func (c *ServerClient) CreateImage(ctx context.Context, server *Server, opts *ServerCreateImageOpts) (ServerCreateImageResult, *Response, error) { - var reqBody schema.ServerActionCreateImageRequest + const opPath = "/servers/%d/actions/create_image" + ctx = ctxutil.SetOpPath(ctx, opPath) + + result := ServerCreateImageResult{} + + reqPath := fmt.Sprintf(opPath, server.ID) + + reqBody := schema.ServerActionCreateImageRequest{} if opts != nil { if err := opts.Validate(); err != nil { - return ServerCreateImageResult{}, nil, fmt.Errorf("invalid options: %s", err) + return result, nil, err } if opts.Description != nil { reqBody.Description = opts.Description @@ -674,26 +627,16 @@ func (c *ServerClient) CreateImage(ctx context.Context, server *Server, opts *Se reqBody.Labels = &opts.Labels } } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return ServerCreateImageResult{}, nil, err - } - path := fmt.Sprintf("/servers/%d/actions/create_image", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) + respBody, resp, err := postRequest[schema.ServerActionCreateImageResponse](ctx, c.client, reqPath, reqBody) if err != nil { - return ServerCreateImageResult{}, nil, err + return result, resp, err } - respBody := schema.ServerActionCreateImageResponse{} - resp, err := c.client.Do(req, &respBody) - if err != nil { - return ServerCreateImageResult{}, resp, err - } - return ServerCreateImageResult{ - Action: ActionFromSchema(respBody.Action), - Image: ImageFromSchema(respBody.Image), - }, resp, nil + result.Image = ImageFromSchema(respBody.Image) + result.Action = ActionFromSchema(respBody.Action) + + return result, resp, nil } // ServerEnableRescueOpts specifies options for enabling rescue mode for a server. @@ -710,48 +653,43 @@ type ServerEnableRescueResult struct { // EnableRescue enables rescue mode for a server. func (c *ServerClient) EnableRescue(ctx context.Context, server *Server, opts ServerEnableRescueOpts) (ServerEnableRescueResult, *Response, error) { + const opPath = "/servers/%d/actions/enable_rescue" + ctx = ctxutil.SetOpPath(ctx, opPath) + + result := ServerEnableRescueResult{} + + reqPath := fmt.Sprintf(opPath, server.ID) + reqBody := schema.ServerActionEnableRescueRequest{ Type: Ptr(string(opts.Type)), } for _, sshKey := range opts.SSHKeys { reqBody.SSHKeys = append(reqBody.SSHKeys, sshKey.ID) } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return ServerEnableRescueResult{}, nil, err - } - path := fmt.Sprintf("/servers/%d/actions/enable_rescue", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) + respBody, resp, err := postRequest[schema.ServerActionEnableRescueResponse](ctx, c.client, reqPath, reqBody) if err != nil { - return ServerEnableRescueResult{}, nil, err + return result, resp, err } - respBody := schema.ServerActionEnableRescueResponse{} - resp, err := c.client.Do(req, &respBody) - if err != nil { - return ServerEnableRescueResult{}, resp, err - } - result := ServerEnableRescueResult{ - Action: ActionFromSchema(respBody.Action), - RootPassword: respBody.RootPassword, - } + result.Action = ActionFromSchema(respBody.Action) + result.RootPassword = respBody.RootPassword + return result, resp, nil } // DisableRescue disables rescue mode for a server. func (c *ServerClient) DisableRescue(ctx context.Context, server *Server) (*Action, *Response, error) { - path := fmt.Sprintf("/servers/%d/actions/disable_rescue", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/servers/%d/actions/disable_rescue" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) - respBody := schema.ServerActionDisableRescueResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionDisableRescueResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -777,32 +715,24 @@ func (c *ServerClient) Rebuild(ctx context.Context, server *Server, opts ServerR // RebuildWithResult rebuilds a server. func (c *ServerClient) RebuildWithResult(ctx context.Context, server *Server, opts ServerRebuildOpts) (ServerRebuildResult, *Response, error) { - reqBody := schema.ServerActionRebuildRequest{} - if opts.Image.ID != 0 { - reqBody.Image = opts.Image.ID - } else { - reqBody.Image = opts.Image.Name - } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return ServerRebuildResult{}, nil, err - } + const opPath = "/servers/%d/actions/rebuild" + ctx = ctxutil.SetOpPath(ctx, opPath) - path := fmt.Sprintf("/servers/%d/actions/rebuild", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return ServerRebuildResult{}, nil, err + result := ServerRebuildResult{} + + reqPath := fmt.Sprintf(opPath, server.ID) + + reqBody := schema.ServerActionRebuildRequest{} + if opts.Image.ID != 0 || opts.Image.Name != "" { + reqBody.Image = schema.IDOrName{ID: opts.Image.ID, Name: opts.Image.Name} } - respBody := schema.ServerActionRebuildResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionRebuildResponse](ctx, c.client, reqPath, reqBody) if err != nil { - return ServerRebuildResult{}, resp, err + return result, resp, err } - result := ServerRebuildResult{ - Action: ActionFromSchema(respBody.Action), - } + result.Action = ActionFromSchema(respBody.Action) if respBody.RootPassword != nil { result.RootPassword = *respBody.RootPassword } @@ -812,87 +742,69 @@ func (c *ServerClient) RebuildWithResult(ctx context.Context, server *Server, op // AttachISO attaches an ISO to a server. func (c *ServerClient) AttachISO(ctx context.Context, server *Server, iso *ISO) (*Action, *Response, error) { - reqBody := schema.ServerActionAttachISORequest{} - if iso.ID != 0 { - reqBody.ISO = iso.ID - } else { - reqBody.ISO = iso.Name - } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } + const opPath = "/servers/%d/actions/attach_iso" + ctx = ctxutil.SetOpPath(ctx, opPath) - path := fmt.Sprintf("/servers/%d/actions/attach_iso", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err + reqPath := fmt.Sprintf(opPath, server.ID) + + reqBody := schema.ServerActionAttachISORequest{} + if iso.ID != 0 || iso.Name != "" { + reqBody.ISO = schema.IDOrName{ID: iso.ID, Name: iso.Name} } - respBody := schema.ServerActionAttachISOResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionAttachISOResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } // DetachISO detaches the currently attached ISO from a server. func (c *ServerClient) DetachISO(ctx context.Context, server *Server) (*Action, *Response, error) { - path := fmt.Sprintf("/servers/%d/actions/detach_iso", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/servers/%d/actions/detach_iso" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) - respBody := schema.ServerActionDetachISOResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionDetachISOResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } -// EnableBackup enables backup for a server. Pass in an empty backup window to let the -// API pick a window for you. See the API documentation at docs.hetzner.cloud for a list -// of valid backup windows. +// EnableBackup enables backup for a server. +// The window parameter is deprecated and will be ignored. func (c *ServerClient) EnableBackup(ctx context.Context, server *Server, window string) (*Action, *Response, error) { - reqBody := schema.ServerActionEnableBackupRequest{} - if window != "" { - reqBody.BackupWindow = Ptr(window) - } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } + _ = window - path := fmt.Sprintf("/servers/%d/actions/enable_backup", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } + const opPath = "/servers/%d/actions/enable_backup" + ctx = ctxutil.SetOpPath(ctx, opPath) - respBody := schema.ServerActionEnableBackupResponse{} - resp, err := c.client.Do(req, &respBody) + reqPath := fmt.Sprintf(opPath, server.ID) + + respBody, resp, err := postRequest[schema.ServerActionEnableBackupResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } // DisableBackup disables backup for a server. func (c *ServerClient) DisableBackup(ctx context.Context, server *Server) (*Action, *Response, error) { - path := fmt.Sprintf("/servers/%d/actions/disable_backup", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/servers/%d/actions/disable_backup" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) - respBody := schema.ServerActionDisableBackupResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionDisableBackupResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -904,30 +816,23 @@ type ServerChangeTypeOpts struct { // ChangeType changes a server's type. func (c *ServerClient) ChangeType(ctx context.Context, server *Server, opts ServerChangeTypeOpts) (*Action, *Response, error) { + const opPath = "/servers/%d/actions/change_type" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) + reqBody := schema.ServerActionChangeTypeRequest{ UpgradeDisk: opts.UpgradeDisk, } - if opts.ServerType.ID != 0 { - reqBody.ServerType = opts.ServerType.ID - } else { - reqBody.ServerType = opts.ServerType.Name - } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/servers/%d/actions/change_type", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err + if opts.ServerType.ID != 0 || opts.ServerType.Name != "" { + reqBody.ServerType = schema.IDOrName{ID: opts.ServerType.ID, Name: opts.ServerType.Name} } - respBody := schema.ServerActionChangeTypeResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionChangeTypeResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -949,27 +854,22 @@ type ServerChangeProtectionOpts struct { // ChangeProtection changes the resource protection level of a server. func (c *ServerClient) ChangeProtection(ctx context.Context, server *Server, opts ServerChangeProtectionOpts) (*Action, *Response, error) { + const opPath = "/servers/%d/actions/change_protection" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) + reqBody := schema.ServerActionChangeProtectionRequest{ Rebuild: opts.Rebuild, Delete: opts.Delete, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/servers/%d/actions/change_protection", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.ServerActionChangeProtectionResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } // ServerRequestConsoleResult is the result of requesting a WebSocket VNC console. @@ -981,22 +881,23 @@ type ServerRequestConsoleResult struct { // RequestConsole requests a WebSocket VNC console. func (c *ServerClient) RequestConsole(ctx context.Context, server *Server) (ServerRequestConsoleResult, *Response, error) { - path := fmt.Sprintf("/servers/%d/actions/request_console", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return ServerRequestConsoleResult{}, nil, err - } + const opPath = "/servers/%d/actions/request_console" + ctx = ctxutil.SetOpPath(ctx, opPath) + + result := ServerRequestConsoleResult{} - respBody := schema.ServerActionRequestConsoleResponse{} - resp, err := c.client.Do(req, &respBody) + reqPath := fmt.Sprintf(opPath, server.ID) + + respBody, resp, err := postRequest[schema.ServerActionRequestConsoleResponse](ctx, c.client, reqPath, nil) if err != nil { - return ServerRequestConsoleResult{}, resp, err + return result, resp, err } - return ServerRequestConsoleResult{ - Action: ActionFromSchema(respBody.Action), - WSSURL: respBody.WSSURL, - Password: respBody.Password, - }, resp, nil + + result.Action = ActionFromSchema(respBody.Action) + result.WSSURL = respBody.WSSURL + result.Password = respBody.Password + + return result, resp, nil } // ServerAttachToNetworkOpts specifies options for attaching a server to a network. @@ -1008,6 +909,11 @@ type ServerAttachToNetworkOpts struct { // AttachToNetwork attaches a server to a network. func (c *ServerClient) AttachToNetwork(ctx context.Context, server *Server, opts ServerAttachToNetworkOpts) (*Action, *Response, error) { + const opPath = "/servers/%d/actions/attach_to_network" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) + reqBody := schema.ServerActionAttachToNetworkRequest{ Network: opts.Network.ID, } @@ -1017,22 +923,12 @@ func (c *ServerClient) AttachToNetwork(ctx context.Context, server *Server, opts for _, aliasIP := range opts.AliasIPs { reqBody.AliasIPs = append(reqBody.AliasIPs, Ptr(aliasIP.String())) } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/servers/%d/actions/attach_to_network", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - respBody := schema.ServerActionAttachToNetworkResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionAttachToNetworkResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, err } @@ -1043,25 +939,20 @@ type ServerDetachFromNetworkOpts struct { // DetachFromNetwork detaches a server from a network. func (c *ServerClient) DetachFromNetwork(ctx context.Context, server *Server, opts ServerDetachFromNetworkOpts) (*Action, *Response, error) { + const opPath = "/servers/%d/actions/detach_from_network" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) + reqBody := schema.ServerActionDetachFromNetworkRequest{ Network: opts.Network.ID, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/servers/%d/actions/detach_from_network", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.ServerActionDetachFromNetworkResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionDetachFromNetworkResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, err } @@ -1073,6 +964,11 @@ type ServerChangeAliasIPsOpts struct { // ChangeAliasIPs changes a server's alias IPs in a network. func (c *ServerClient) ChangeAliasIPs(ctx context.Context, server *Server, opts ServerChangeAliasIPsOpts) (*Action, *Response, error) { + const opPath = "/servers/%d/actions/change_alias_ips" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) + reqBody := schema.ServerActionChangeAliasIPsRequest{ Network: opts.Network.ID, AliasIPs: []string{}, @@ -1080,21 +976,12 @@ func (c *ServerClient) ChangeAliasIPs(ctx context.Context, server *Server, opts for _, aliasIP := range opts.AliasIPs { reqBody.AliasIPs = append(reqBody.AliasIPs, aliasIP.String()) } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/servers/%d/actions/change_alias_ips", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.ServerActionDetachFromNetworkResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionChangeAliasIPsResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, err } @@ -1117,32 +1004,34 @@ type ServerGetMetricsOpts struct { Step int } -func (o *ServerGetMetricsOpts) addQueryParams(req *http.Request) error { - query := req.URL.Query() - +func (o ServerGetMetricsOpts) Validate() error { if len(o.Types) == 0 { - return fmt.Errorf("no metric types specified") + return missingField(o, "Types") + } + if o.Start.IsZero() { + return missingField(o, "Start") } + if o.End.IsZero() { + return missingField(o, "End") + } + return nil +} + +func (o ServerGetMetricsOpts) values() url.Values { + query := url.Values{} + for _, typ := range o.Types { query.Add("type", string(typ)) } - if o.Start.IsZero() { - return fmt.Errorf("no start time specified") - } query.Add("start", o.Start.Format(time.RFC3339)) - - if o.End.IsZero() { - return fmt.Errorf("no end time specified") - } query.Add("end", o.End.Format(time.RFC3339)) if o.Step > 0 { query.Add("step", strconv.Itoa(o.Step)) } - req.URL.RawQuery = query.Encode() - return nil + return query } // ServerMetrics contains the metrics requested for a Server. @@ -1161,64 +1050,60 @@ type ServerMetricsValue struct { // GetMetrics obtains metrics for Server. func (c *ServerClient) GetMetrics(ctx context.Context, server *Server, opts ServerGetMetricsOpts) (*ServerMetrics, *Response, error) { - var respBody schema.ServerGetMetricsResponse + const opPath = "/servers/%d/metrics?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) if server == nil { - return nil, nil, fmt.Errorf("illegal argument: server is nil") + return nil, nil, missingArgument("server", server) } - path := fmt.Sprintf("/servers/%d/metrics", server.ID) - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, fmt.Errorf("new request: %v", err) - } - if err := opts.addQueryParams(req); err != nil { - return nil, nil, fmt.Errorf("add query params: %v", err) + if err := opts.Validate(); err != nil { + return nil, nil, err } - resp, err := c.client.Do(req, &respBody) + + reqPath := fmt.Sprintf(opPath, server.ID, opts.values().Encode()) + + respBody, resp, err := getRequest[schema.ServerGetMetricsResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, fmt.Errorf("get metrics: %v", err) + return nil, resp, err } - ms, err := serverMetricsFromSchema(&respBody) + + metrics, err := serverMetricsFromSchema(&respBody) if err != nil { - return nil, nil, fmt.Errorf("convert response body: %v", err) + return nil, nil, fmt.Errorf("convert response body: %w", err) } - return ms, resp, nil + + return metrics, resp, nil } func (c *ServerClient) AddToPlacementGroup(ctx context.Context, server *Server, placementGroup *PlacementGroup) (*Action, *Response, error) { + const opPath = "/servers/%d/actions/add_to_placement_group" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) + reqBody := schema.ServerActionAddToPlacementGroupRequest{ PlacementGroup: placementGroup.ID, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/servers/%d/actions/add_to_placement_group", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.ServerActionAddToPlacementGroupResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionAddToPlacementGroupResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } func (c *ServerClient) RemoveFromPlacementGroup(ctx context.Context, server *Server) (*Action, *Response, error) { - path := fmt.Sprintf("/servers/%d/actions/remove_from_placement_group", server.ID) - req, err := c.client.NewRequest(ctx, "POST", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/servers/%d/actions/remove_from_placement_group" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, server.ID) - respBody := schema.ServerActionRemoveFromPlacementGroupResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.ServerActionRemoveFromPlacementGroupResponse](ctx, c.client, reqPath, nil) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server_type.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server_type.go index 9640a2ac5482a8e931e4d56e51044d72d18d5d47..261f0c417f0df1b801c70cb102972d114745cf09 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server_type.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server_type.go @@ -6,6 +6,7 @@ import ( "net/url" "strconv" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -20,7 +21,9 @@ type ServerType struct { StorageType StorageType CPUType CPUType Architecture Architecture - // IncludedTraffic is the free traffic per month in bytes + + // Deprecated: [ServerType.IncludedTraffic] is deprecated and will always report 0 after 2024-08-05. + // Use [ServerType.Pricings] instead to get the included traffic for each location. IncludedTraffic int64 Pricings []ServerTypeLocationPricing DeprecatableResource @@ -55,32 +58,27 @@ type ServerTypeClient struct { // GetByID retrieves a server type by its ID. If the server type does not exist, nil is returned. func (c *ServerTypeClient) GetByID(ctx context.Context, id int64) (*ServerType, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/server_types/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/server_types/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) - var body schema.ServerTypeGetResponse - resp, err := c.client.Do(req, &body) + reqPath := fmt.Sprintf(opPath, id) + + respBody, resp, err := getRequest[schema.ServerTypeGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return ServerTypeFromSchema(body.ServerType), resp, nil + + return ServerTypeFromSchema(respBody.ServerType), resp, nil } // GetByName retrieves a server type by its name. If the server type does not exist, nil is returned. func (c *ServerTypeClient) GetByName(ctx context.Context, name string) (*ServerType, *Response, error) { - if name == "" { - return nil, nil, nil - } - serverTypes, response, err := c.List(ctx, ServerTypeListOpts{Name: name}) - if len(serverTypes) == 0 { - return nil, response, err - } - return serverTypes[0], response, err + return firstByName(name, func() ([]*ServerType, *Response, error) { + return c.List(ctx, ServerTypeListOpts{Name: name}) + }) } // Get retrieves a server type by its ID if the input can be parsed as an integer, otherwise it @@ -115,22 +113,17 @@ func (l ServerTypeListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *ServerTypeClient) List(ctx context.Context, opts ServerTypeListOpts) ([]*ServerType, *Response, error) { - path := "/server_types?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/server_types?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) - var body schema.ServerTypeListResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.ServerTypeListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - serverTypes := make([]*ServerType, 0, len(body.ServerTypes)) - for _, s := range body.ServerTypes { - serverTypes = append(serverTypes, ServerTypeFromSchema(s)) + return nil, resp, err } - return serverTypes, resp, nil + + return allFromSchemaFunc(respBody.ServerTypes, ServerTypeFromSchema), resp, nil } // All returns all server types. @@ -140,20 +133,8 @@ func (c *ServerTypeClient) All(ctx context.Context) ([]*ServerType, error) { // AllWithOpts returns all server types for the given options. func (c *ServerTypeClient) AllWithOpts(ctx context.Context, opts ServerTypeListOpts) ([]*ServerType, error) { - allServerTypes := []*ServerType{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*ServerType, *Response, error) { opts.Page = page - serverTypes, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allServerTypes = append(allServerTypes, serverTypes...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allServerTypes, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/ssh_key.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/ssh_key.go index 40537606c63354bdb4953eeacd873a40c865028e..ba6e5e65938187efb8d37fc68f7aac1eb2de1b61 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/ssh_key.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/ssh_key.go @@ -1,15 +1,12 @@ package hcloud import ( - "bytes" "context" - "encoding/json" - "errors" "fmt" "net/url" - "strconv" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -30,50 +27,40 @@ type SSHKeyClient struct { // GetByID retrieves a SSH key by its ID. If the SSH key does not exist, nil is returned. func (c *SSHKeyClient) GetByID(ctx context.Context, id int64) (*SSHKey, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/ssh_keys/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/ssh_keys/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.SSHKeyGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.SSHKeyGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return SSHKeyFromSchema(body.SSHKey), resp, nil + + return SSHKeyFromSchema(respBody.SSHKey), resp, nil } // GetByName retrieves a SSH key by its name. If the SSH key does not exist, nil is returned. func (c *SSHKeyClient) GetByName(ctx context.Context, name string) (*SSHKey, *Response, error) { - if name == "" { - return nil, nil, nil - } - sshKeys, response, err := c.List(ctx, SSHKeyListOpts{Name: name}) - if len(sshKeys) == 0 { - return nil, response, err - } - return sshKeys[0], response, err + return firstByName(name, func() ([]*SSHKey, *Response, error) { + return c.List(ctx, SSHKeyListOpts{Name: name}) + }) } // GetByFingerprint retreives a SSH key by its fingerprint. If the SSH key does not exist, nil is returned. func (c *SSHKeyClient) GetByFingerprint(ctx context.Context, fingerprint string) (*SSHKey, *Response, error) { - sshKeys, response, err := c.List(ctx, SSHKeyListOpts{Fingerprint: fingerprint}) - if len(sshKeys) == 0 { - return nil, response, err - } - return sshKeys[0], response, err + return firstBy(func() ([]*SSHKey, *Response, error) { + return c.List(ctx, SSHKeyListOpts{Fingerprint: fingerprint}) + }) } // Get retrieves a SSH key by its ID if the input can be parsed as an integer, otherwise it // retrieves a SSH key by its name. If the SSH key does not exist, nil is returned. func (c *SSHKeyClient) Get(ctx context.Context, idOrName string) (*SSHKey, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByName(ctx, idOrName) + return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName) } // SSHKeyListOpts specifies options for listing SSH keys. @@ -103,22 +90,17 @@ func (l SSHKeyListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *SSHKeyClient) List(ctx context.Context, opts SSHKeyListOpts) ([]*SSHKey, *Response, error) { - path := "/ssh_keys?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/ssh_keys?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) - var body schema.SSHKeyListResponse - resp, err := c.client.Do(req, &body) + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) + + respBody, resp, err := getRequest[schema.SSHKeyListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - sshKeys := make([]*SSHKey, 0, len(body.SSHKeys)) - for _, s := range body.SSHKeys { - sshKeys = append(sshKeys, SSHKeyFromSchema(s)) + return nil, resp, err } - return sshKeys, resp, nil + + return allFromSchemaFunc(respBody.SSHKeys, SSHKeyFromSchema), resp, nil } // All returns all SSH keys. @@ -128,22 +110,10 @@ func (c *SSHKeyClient) All(ctx context.Context) ([]*SSHKey, error) { // AllWithOpts returns all SSH keys with the given options. func (c *SSHKeyClient) AllWithOpts(ctx context.Context, opts SSHKeyListOpts) ([]*SSHKey, error) { - allSSHKeys := []*SSHKey{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*SSHKey, *Response, error) { opts.Page = page - sshKeys, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allSSHKeys = append(allSSHKeys, sshKeys...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allSSHKeys, nil } // SSHKeyCreateOpts specifies parameters for creating a SSH key. @@ -156,16 +126,21 @@ type SSHKeyCreateOpts struct { // Validate checks if options are valid. func (o SSHKeyCreateOpts) Validate() error { if o.Name == "" { - return errors.New("missing name") + return missingField(o, "Name") } if o.PublicKey == "" { - return errors.New("missing public key") + return missingField(o, "PublicKey") } return nil } // Create creates a new SSH key with the given options. func (c *SSHKeyClient) Create(ctx context.Context, opts SSHKeyCreateOpts) (*SSHKey, *Response, error) { + const opPath = "/ssh_keys" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := opPath + if err := opts.Validate(); err != nil { return nil, nil, err } @@ -176,31 +151,23 @@ func (c *SSHKeyClient) Create(ctx context.Context, opts SSHKeyCreateOpts) (*SSHK if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - req, err := c.client.NewRequest(ctx, "POST", "/ssh_keys", bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - var respBody schema.SSHKeyCreateResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.SSHKeyCreateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return SSHKeyFromSchema(respBody.SSHKey), resp, nil } // Delete deletes a SSH key. func (c *SSHKeyClient) Delete(ctx context.Context, sshKey *SSHKey) (*Response, error) { - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/ssh_keys/%d", sshKey.ID), nil) - if err != nil { - return nil, err - } - return c.client.Do(req, nil) + const opPath = "/ssh_keys/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, sshKey.ID) + + return deleteRequestNoResult(ctx, c.client, reqPath) } // SSHKeyUpdateOpts specifies options for updating a SSH key. @@ -211,27 +178,22 @@ type SSHKeyUpdateOpts struct { // Update updates a SSH key. func (c *SSHKeyClient) Update(ctx context.Context, sshKey *SSHKey, opts SSHKeyUpdateOpts) (*SSHKey, *Response, error) { + const opPath = "/ssh_keys/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, sshKey.ID) + reqBody := schema.SSHKeyUpdateRequest{ Name: opts.Name, } if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/ssh_keys/%d", sshKey.ID) - req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - respBody := schema.SSHKeyUpdateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := putRequest[schema.SSHKeyUpdateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return SSHKeyFromSchema(respBody.SSHKey), resp, nil } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/volume.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/volume.go index 225a23a52033b5bc134cdddc68a4e489a464b198..78685341cf4b902ca11c2dd8da779a63af435c42 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/volume.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/volume.go @@ -1,15 +1,12 @@ package hcloud import ( - "bytes" "context" - "encoding/json" - "errors" "fmt" "net/url" - "strconv" "time" + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/exp/ctxutil" "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" ) @@ -57,41 +54,33 @@ const ( // GetByID retrieves a volume by its ID. If the volume does not exist, nil is returned. func (c *VolumeClient) GetByID(ctx context.Context, id int64) (*Volume, *Response, error) { - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/volumes/%d", id), nil) - if err != nil { - return nil, nil, err - } + const opPath = "/volumes/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, id) - var body schema.VolumeGetResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.VolumeGetResponse](ctx, c.client, reqPath) if err != nil { if IsError(err, ErrorCodeNotFound) { return nil, resp, nil } - return nil, nil, err + return nil, resp, err } - return VolumeFromSchema(body.Volume), resp, nil + + return VolumeFromSchema(respBody.Volume), resp, nil } // GetByName retrieves a volume by its name. If the volume does not exist, nil is returned. func (c *VolumeClient) GetByName(ctx context.Context, name string) (*Volume, *Response, error) { - if name == "" { - return nil, nil, nil - } - volumes, response, err := c.List(ctx, VolumeListOpts{Name: name}) - if len(volumes) == 0 { - return nil, response, err - } - return volumes[0], response, err + return firstByName(name, func() ([]*Volume, *Response, error) { + return c.List(ctx, VolumeListOpts{Name: name}) + }) } // Get retrieves a volume by its ID if the input can be parsed as an integer, otherwise it // retrieves a volume by its name. If the volume does not exist, nil is returned. func (c *VolumeClient) Get(ctx context.Context, idOrName string) (*Volume, *Response, error) { - if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil { - return c.GetByID(ctx, id) - } - return c.GetByName(ctx, idOrName) + return getByIDOrName(ctx, c.GetByID, c.GetByName, idOrName) } // VolumeListOpts specifies options for listing volumes. @@ -121,22 +110,17 @@ func (l VolumeListOpts) values() url.Values { // Please note that filters specified in opts are not taken into account // when their value corresponds to their zero value or when they are empty. func (c *VolumeClient) List(ctx context.Context, opts VolumeListOpts) ([]*Volume, *Response, error) { - path := "/volumes?" + opts.values().Encode() - req, err := c.client.NewRequest(ctx, "GET", path, nil) - if err != nil { - return nil, nil, err - } + const opPath = "/volumes?%s" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, opts.values().Encode()) - var body schema.VolumeListResponse - resp, err := c.client.Do(req, &body) + respBody, resp, err := getRequest[schema.VolumeListResponse](ctx, c.client, reqPath) if err != nil { - return nil, nil, err - } - volumes := make([]*Volume, 0, len(body.Volumes)) - for _, s := range body.Volumes { - volumes = append(volumes, VolumeFromSchema(s)) + return nil, resp, err } - return volumes, resp, nil + + return allFromSchemaFunc(respBody.Volumes, VolumeFromSchema), resp, nil } // All returns all volumes. @@ -146,22 +130,10 @@ func (c *VolumeClient) All(ctx context.Context) ([]*Volume, error) { // AllWithOpts returns all volumes with the given options. func (c *VolumeClient) AllWithOpts(ctx context.Context, opts VolumeListOpts) ([]*Volume, error) { - allVolumes := []*Volume{} - - err := c.client.all(func(page int) (*Response, error) { + return iterPages(func(page int) ([]*Volume, *Response, error) { opts.Page = page - volumes, resp, err := c.List(ctx, opts) - if err != nil { - return resp, err - } - allVolumes = append(allVolumes, volumes...) - return resp, nil + return c.List(ctx, opts) }) - if err != nil { - return nil, err - } - - return allVolumes, nil } // VolumeCreateOpts specifies parameters for creating a volume. @@ -178,19 +150,19 @@ type VolumeCreateOpts struct { // Validate checks if options are valid. func (o VolumeCreateOpts) Validate() error { if o.Name == "" { - return errors.New("missing name") + return missingField(o, "Name") } if o.Size <= 0 { - return errors.New("size must be greater than 0") + return invalidFieldValue(o, "Size", o.Size) } if o.Server == nil && o.Location == nil { - return errors.New("one of server or location must be provided") + return missingOneOfFields(o, "Server", "Location") } if o.Server != nil && o.Location != nil { - return errors.New("only one of server or location must be provided") + return mutuallyExclusiveFields(o, "Server", "Location") } if o.Server == nil && (o.Automount != nil && *o.Automount) { - return errors.New("server must be provided when automount is true") + return missingRequiredTogetherFields(o, "Automount", "Server") } return nil } @@ -204,8 +176,15 @@ type VolumeCreateResult struct { // Create creates a new volume with the given options. func (c *VolumeClient) Create(ctx context.Context, opts VolumeCreateOpts) (VolumeCreateResult, *Response, error) { + const opPath = "/volumes" + ctx = ctxutil.SetOpPath(ctx, opPath) + + result := VolumeCreateResult{} + + reqPath := opPath + if err := opts.Validate(); err != nil { - return VolumeCreateResult{}, nil, err + return result, nil, err } reqBody := schema.VolumeCreateRequest{ Name: opts.Name, @@ -220,48 +199,33 @@ func (c *VolumeClient) Create(ctx context.Context, opts VolumeCreateOpts) (Volum reqBody.Server = Ptr(opts.Server.ID) } if opts.Location != nil { - if opts.Location.ID != 0 { - reqBody.Location = opts.Location.ID - } else { - reqBody.Location = opts.Location.Name + if opts.Location.ID != 0 || opts.Location.Name != "" { + reqBody.Location = &schema.IDOrName{ID: opts.Location.ID, Name: opts.Location.Name} } } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return VolumeCreateResult{}, nil, err - } - - req, err := c.client.NewRequest(ctx, "POST", "/volumes", bytes.NewReader(reqBodyData)) - if err != nil { - return VolumeCreateResult{}, nil, err - } - - var respBody schema.VolumeCreateResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.VolumeCreateResponse](ctx, c.client, reqPath, reqBody) if err != nil { - return VolumeCreateResult{}, resp, err + return result, resp, err } - var action *Action + result.Volume = VolumeFromSchema(respBody.Volume) if respBody.Action != nil { - action = ActionFromSchema(*respBody.Action) + result.Action = ActionFromSchema(*respBody.Action) } + result.NextActions = ActionsFromSchema(respBody.NextActions) - return VolumeCreateResult{ - Volume: VolumeFromSchema(respBody.Volume), - Action: action, - NextActions: ActionsFromSchema(respBody.NextActions), - }, resp, nil + return result, resp, nil } // Delete deletes a volume. func (c *VolumeClient) Delete(ctx context.Context, volume *Volume) (*Response, error) { - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/volumes/%d", volume.ID), nil) - if err != nil { - return nil, err - } - return c.client.Do(req, nil) + const opPath = "/volumes/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, volume.ID) + + return deleteRequestNoResult(ctx, c.client, reqPath) } // VolumeUpdateOpts specifies options for updating a volume. @@ -272,28 +236,23 @@ type VolumeUpdateOpts struct { // Update updates a volume. func (c *VolumeClient) Update(ctx context.Context, volume *Volume, opts VolumeUpdateOpts) (*Volume, *Response, error) { + const opPath = "/volumes/%d" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, volume.ID) + reqBody := schema.VolumeUpdateRequest{ Name: opts.Name, } if opts.Labels != nil { reqBody.Labels = &opts.Labels } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/volumes/%d", volume.ID) - req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - respBody := schema.VolumeUpdateResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := putRequest[schema.VolumeUpdateResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return VolumeFromSchema(respBody.Volume), resp, nil } @@ -305,27 +264,21 @@ type VolumeAttachOpts struct { // AttachWithOpts attaches a volume to a server. func (c *VolumeClient) AttachWithOpts(ctx context.Context, volume *Volume, opts VolumeAttachOpts) (*Action, *Response, error) { + const opPath = "/volumes/%d/actions/attach" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, volume.ID) + reqBody := schema.VolumeActionAttachVolumeRequest{ Server: opts.Server.ID, Automount: opts.Automount, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - - path := fmt.Sprintf("/volumes/%d/actions/attach", volume.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - var respBody schema.VolumeActionAttachVolumeResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.VolumeActionAttachVolumeResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -336,23 +289,18 @@ func (c *VolumeClient) Attach(ctx context.Context, volume *Volume, server *Serve // Detach detaches a volume from a server. func (c *VolumeClient) Detach(ctx context.Context, volume *Volume) (*Action, *Response, error) { - var reqBody schema.VolumeActionDetachVolumeRequest - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } + const opPath = "/volumes/%d/actions/detach" + ctx = ctxutil.SetOpPath(ctx, opPath) - path := fmt.Sprintf("/volumes/%d/actions/detach", volume.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } + reqPath := fmt.Sprintf(opPath, volume.ID) + + var reqBody schema.VolumeActionDetachVolumeRequest - var respBody schema.VolumeActionDetachVolumeResponse - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.VolumeActionDetachVolumeResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, nil } @@ -363,48 +311,38 @@ type VolumeChangeProtectionOpts struct { // ChangeProtection changes the resource protection level of a volume. func (c *VolumeClient) ChangeProtection(ctx context.Context, volume *Volume, opts VolumeChangeProtectionOpts) (*Action, *Response, error) { + const opPath = "/volumes/%d/actions/change_protection" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, volume.ID) + reqBody := schema.VolumeActionChangeProtectionRequest{ Delete: opts.Delete, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/volumes/%d/actions/change_protection", volume.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - respBody := schema.VolumeActionChangeProtectionResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.VolumeActionChangeProtectionResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } - return ActionFromSchema(respBody.Action), resp, err + + return ActionFromSchema(respBody.Action), resp, nil } // Resize changes the size of a volume. func (c *VolumeClient) Resize(ctx context.Context, volume *Volume, size int) (*Action, *Response, error) { + const opPath = "/volumes/%d/actions/resize" + ctx = ctxutil.SetOpPath(ctx, opPath) + + reqPath := fmt.Sprintf(opPath, volume.ID) + reqBody := schema.VolumeActionResizeVolumeRequest{ Size: size, } - reqBodyData, err := json.Marshal(reqBody) - if err != nil { - return nil, nil, err - } - path := fmt.Sprintf("/volumes/%d/actions/resize", volume.ID) - req, err := c.client.NewRequest(ctx, "POST", path, bytes.NewReader(reqBodyData)) - if err != nil { - return nil, nil, err - } - - respBody := schema.VolumeActionResizeVolumeResponse{} - resp, err := c.client.Do(req, &respBody) + respBody, resp, err := postRequest[schema.VolumeActionResizeVolumeResponse](ctx, c.client, reqPath, reqBody) if err != nil { return nil, resp, err } + return ActionFromSchema(respBody.Action), resp, err } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_action_client_iface.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_action_client_iface.go index 13f8665dfd22e9f11ab7a6313d5e6d9c4a4744a2..ec4d3bfb097d2614a4239ea077760277e1fe59aa 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_action_client_iface.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_action_client_iface.go @@ -16,8 +16,12 @@ type IActionClient interface { // when their value corresponds to their zero value or when they are empty. List(ctx context.Context, opts ActionListOpts) ([]*Action, *Response, error) // All returns all actions. + // + // Deprecated: It is required to pass in a list of IDs since 30 January 2025. Please use [ActionClient.AllWithOpts] instead. All(ctx context.Context) ([]*Action, error) // AllWithOpts returns all actions for the given options. + // + // It is required to set [ActionListOpts.ID]. Any other fields set in the opts are ignored. AllWithOpts(ctx context.Context, opts ActionListOpts) ([]*Action, error) // WatchOverallProgress watches several actions' progress until they complete // with success or error. This watching happens in a goroutine and updates are @@ -35,7 +39,7 @@ type IActionClient interface { // timeout, use the [context.Context]. Once the method has stopped watching, // both returned channels are closed. // - // WatchOverallProgress uses the [WithPollBackoffFunc] of the [Client] to wait + // WatchOverallProgress uses the [WithPollOpts] of the [Client] to wait // until sending the next request. // // Deprecated: WatchOverallProgress is deprecated, use [WaitForFunc] instead. @@ -56,19 +60,19 @@ type IActionClient interface { // timeout, use the [context.Context]. Once the method has stopped watching, // both returned channels are closed. // - // WatchProgress uses the [WithPollBackoffFunc] of the [Client] to wait until + // WatchProgress uses the [WithPollOpts] of the [Client] to wait until // sending the next request. // // Deprecated: WatchProgress is deprecated, use [WaitForFunc] instead. WatchProgress(ctx context.Context, action *Action) (<-chan int, <-chan error) // WaitForFunc waits until all actions are completed by polling the API at the interval - // defined by [WithPollBackoffFunc]. An action is considered as complete when its status is + // defined by [WithPollOpts]. An action is considered as complete when its status is // either [ActionStatusSuccess] or [ActionStatusError]. // // The handleUpdate callback is called every time an action is updated. WaitForFunc(ctx context.Context, handleUpdate func(update *Action) error, actions ...*Action) error // WaitFor waits until all actions succeed by polling the API at the interval defined by - // [WithPollBackoffFunc]. An action is considered as succeeded when its status is either + // [WithPollOpts]. An action is considered as succeeded when its status is either // [ActionStatusSuccess]. // // If a single action fails, the function will stop waiting and the error set in the diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_load_balancer_client_iface.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_load_balancer_client_iface.go index 2665f22b02d04a14f8e10ad2cd5bbdc62c68c2a7..16a635954a3cdd436ea8e83e5ae3279189cf877f 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_load_balancer_client_iface.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_load_balancer_client_iface.go @@ -64,7 +64,7 @@ type ILoadBalancerClient interface { // ChangeType changes a Load Balancer's type. ChangeType(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerChangeTypeOpts) (*Action, *Response, error) // GetMetrics obtains metrics for a Load Balancer. - GetMetrics(ctx context.Context, lb *LoadBalancer, opts LoadBalancerGetMetricsOpts) (*LoadBalancerMetrics, *Response, error) + GetMetrics(ctx context.Context, loadBalancer *LoadBalancer, opts LoadBalancerGetMetricsOpts) (*LoadBalancerMetrics, *Response, error) // ChangeDNSPtr changes or resets the reverse DNS pointer for a Load Balancer. // Pass a nil ptr to reset the reverse DNS pointer to its default value. ChangeDNSPtr(ctx context.Context, lb *LoadBalancer, ip string, ptr *string) (*Action, *Response, error) diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_primary_ip_client_iface.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_primary_ip_client_iface.go index 6b8e617e233b0a59b7882f7e459f918fd7df8334..94390d475a2beaa4272d29b8b3db6cac8a4918bd 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_primary_ip_client_iface.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_primary_ip_client_iface.go @@ -27,11 +27,11 @@ type IPrimaryIPClient interface { // AllWithOpts returns all Primary IPs for the given options. AllWithOpts(ctx context.Context, opts PrimaryIPListOpts) ([]*PrimaryIP, error) // Create creates a Primary IP. - Create(ctx context.Context, reqBody PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error) + Create(ctx context.Context, opts PrimaryIPCreateOpts) (*PrimaryIPCreateResult, *Response, error) // Delete deletes a Primary IP. Delete(ctx context.Context, primaryIP *PrimaryIP) (*Response, error) // Update updates a Primary IP. - Update(ctx context.Context, primaryIP *PrimaryIP, reqBody PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error) + Update(ctx context.Context, primaryIP *PrimaryIP, opts PrimaryIPUpdateOpts) (*PrimaryIP, *Response, error) // Assign a Primary IP to a resource. Assign(ctx context.Context, opts PrimaryIPAssignOpts) (*Action, *Response, error) // Unassign a Primary IP from a resource. diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_schema.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_schema.go old mode 100755 new mode 100644 index 66fe08d3a8e87361a98a564388f4cf0f13c78b5b..e9d63b8411943021cd0508b576a9fce6a246810c --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_schema.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_schema.go @@ -23,28 +23,22 @@ func (c *converterImpl) ActionFromSchema(source schema.Action) *Action { if source.Error != nil { pString = &source.Error.Code } - var xstring string if pString != nil { - xstring = *pString + hcloudAction.ErrorCode = *pString } - hcloudAction.ErrorCode = xstring var pString2 *string if source.Error != nil { pString2 = &source.Error.Message } - var xstring2 string if pString2 != nil { - xstring2 = *pString2 + hcloudAction.ErrorMessage = *pString2 } - hcloudAction.ErrorMessage = xstring2 - var pHcloudActionResourceList []*ActionResource if source.Resources != nil { - pHcloudActionResourceList = make([]*ActionResource, len(source.Resources)) + hcloudAction.Resources = make([]*ActionResource, len(source.Resources)) for i := 0; i < len(source.Resources); i++ { - pHcloudActionResourceList[i] = c.schemaActionResourceReferenceToPHcloudActionResource(source.Resources[i]) + hcloudAction.Resources[i] = c.schemaActionResourceReferenceToPHcloudActionResource(source.Resources[i]) } } - hcloudAction.Resources = pHcloudActionResourceList return &hcloudAction } func (c *converterImpl) ActionsFromSchema(source []schema.Action) []*Action { @@ -70,14 +64,12 @@ func (c *converterImpl) CertificateFromSchema(source schema.Certificate) *Certif hcloudCertificate.DomainNames = source.DomainNames hcloudCertificate.Fingerprint = source.Fingerprint hcloudCertificate.Status = c.pSchemaCertificateStatusRefToPHcloudCertificateStatus(source.Status) - var hcloudCertificateUsedByRefList []CertificateUsedByRef if source.UsedBy != nil { - hcloudCertificateUsedByRefList = make([]CertificateUsedByRef, len(source.UsedBy)) + hcloudCertificate.UsedBy = make([]CertificateUsedByRef, len(source.UsedBy)) for i := 0; i < len(source.UsedBy); i++ { - hcloudCertificateUsedByRefList[i] = c.schemaCertificateUsedByRefToHcloudCertificateUsedByRef(source.UsedBy[i]) + hcloudCertificate.UsedBy[i] = c.schemaCertificateUsedByRefToHcloudCertificateUsedByRef(source.UsedBy[i]) } } - hcloudCertificate.UsedBy = hcloudCertificateUsedByRefList return &hcloudCertificate } func (c *converterImpl) DatacenterFromSchema(source schema.Datacenter) *Datacenter { @@ -112,42 +104,34 @@ func (c *converterImpl) FirewallFromSchema(source schema.Firewall) *Firewall { hcloudFirewall.Name = source.Name hcloudFirewall.Labels = source.Labels hcloudFirewall.Created = c.timeTimeToTimeTime(source.Created) - var hcloudFirewallRuleList []FirewallRule if source.Rules != nil { - hcloudFirewallRuleList = make([]FirewallRule, len(source.Rules)) + hcloudFirewall.Rules = make([]FirewallRule, len(source.Rules)) for i := 0; i < len(source.Rules); i++ { - hcloudFirewallRuleList[i] = c.schemaFirewallRuleToHcloudFirewallRule(source.Rules[i]) + hcloudFirewall.Rules[i] = c.schemaFirewallRuleToHcloudFirewallRule(source.Rules[i]) } } - hcloudFirewall.Rules = hcloudFirewallRuleList - var hcloudFirewallResourceList []FirewallResource if source.AppliedTo != nil { - hcloudFirewallResourceList = make([]FirewallResource, len(source.AppliedTo)) + hcloudFirewall.AppliedTo = make([]FirewallResource, len(source.AppliedTo)) for j := 0; j < len(source.AppliedTo); j++ { - hcloudFirewallResourceList[j] = c.schemaFirewallResourceToHcloudFirewallResource(source.AppliedTo[j]) + hcloudFirewall.AppliedTo[j] = c.schemaFirewallResourceToHcloudFirewallResource(source.AppliedTo[j]) } } - hcloudFirewall.AppliedTo = hcloudFirewallResourceList return &hcloudFirewall } func (c *converterImpl) FloatingIPFromSchema(source schema.FloatingIP) *FloatingIP { var hcloudFloatingIP FloatingIP hcloudFloatingIP.ID = source.ID - var xstring string if source.Description != nil { - xstring = *source.Description + hcloudFloatingIP.Description = *source.Description } - hcloudFloatingIP.Description = xstring hcloudFloatingIP.Created = c.timeTimeToTimeTime(source.Created) hcloudFloatingIP.IP = ipFromFloatingIPSchema(source) hcloudFloatingIP.Network = networkFromFloatingIPSchema(source) hcloudFloatingIP.Type = FloatingIPType(source.Type) - var pHcloudServer *Server if source.Server != nil { hcloudServer := serverFromInt64(*source.Server) - pHcloudServer = &hcloudServer + hcloudFloatingIP.Server = &hcloudServer } - hcloudFloatingIP.Server = pHcloudServer hcloudFloatingIP.DNSPtr = mapFromFloatingIPDNSPtrSchema(source.DNSPtr) hcloudFloatingIP.HomeLocation = c.LocationFromSchema(source.HomeLocation) hcloudFloatingIP.Blocked = source.Blocked @@ -163,35 +147,27 @@ func (c *converterImpl) ISOFromSchema(source schema.ISO) *ISO { func (c *converterImpl) ImageFromSchema(source schema.Image) *Image { var hcloudImage Image hcloudImage.ID = source.ID - var xstring string if source.Name != nil { - xstring = *source.Name + hcloudImage.Name = *source.Name } - hcloudImage.Name = xstring hcloudImage.Type = ImageType(source.Type) hcloudImage.Status = ImageStatus(source.Status) hcloudImage.Description = source.Description - var xfloat32 float32 if source.ImageSize != nil { - xfloat32 = *source.ImageSize + hcloudImage.ImageSize = *source.ImageSize } - hcloudImage.ImageSize = xfloat32 hcloudImage.DiskSize = source.DiskSize hcloudImage.Created = c.pTimeTimeToTimeTime(source.Created) hcloudImage.CreatedFrom = c.pSchemaImageCreatedFromToPHcloudServer(source.CreatedFrom) - var pHcloudServer *Server if source.BoundTo != nil { hcloudServer := serverFromInt64(*source.BoundTo) - pHcloudServer = &hcloudServer + hcloudImage.BoundTo = &hcloudServer } - hcloudImage.BoundTo = pHcloudServer hcloudImage.RapidDeploy = source.RapidDeploy hcloudImage.OSFlavor = source.OSFlavor - var xstring2 string if source.OSVersion != nil { - xstring2 = *source.OSVersion + hcloudImage.OSVersion = *source.OSVersion } - hcloudImage.OSVersion = xstring2 hcloudImage.Architecture = Architecture(source.Architecture) hcloudImage.Protection = c.schemaImageProtectionToHcloudImageProtection(source.Protection) hcloudImage.Deprecated = c.pTimeTimeToTimeTime(source.Deprecated) @@ -204,47 +180,37 @@ func (c *converterImpl) LoadBalancerFromSchema(source schema.LoadBalancer) *Load hcloudLoadBalancer.ID = source.ID hcloudLoadBalancer.Name = source.Name hcloudLoadBalancer.PublicNet = c.schemaLoadBalancerPublicNetToHcloudLoadBalancerPublicNet(source.PublicNet) - var hcloudLoadBalancerPrivateNetList []LoadBalancerPrivateNet if source.PrivateNet != nil { - hcloudLoadBalancerPrivateNetList = make([]LoadBalancerPrivateNet, len(source.PrivateNet)) + hcloudLoadBalancer.PrivateNet = make([]LoadBalancerPrivateNet, len(source.PrivateNet)) for i := 0; i < len(source.PrivateNet); i++ { - hcloudLoadBalancerPrivateNetList[i] = c.schemaLoadBalancerPrivateNetToHcloudLoadBalancerPrivateNet(source.PrivateNet[i]) + hcloudLoadBalancer.PrivateNet[i] = c.schemaLoadBalancerPrivateNetToHcloudLoadBalancerPrivateNet(source.PrivateNet[i]) } } - hcloudLoadBalancer.PrivateNet = hcloudLoadBalancerPrivateNetList hcloudLoadBalancer.Location = c.LocationFromSchema(source.Location) hcloudLoadBalancer.LoadBalancerType = c.LoadBalancerTypeFromSchema(source.LoadBalancerType) hcloudLoadBalancer.Algorithm = c.schemaLoadBalancerAlgorithmToHcloudLoadBalancerAlgorithm(source.Algorithm) - var hcloudLoadBalancerServiceList []LoadBalancerService if source.Services != nil { - hcloudLoadBalancerServiceList = make([]LoadBalancerService, len(source.Services)) + hcloudLoadBalancer.Services = make([]LoadBalancerService, len(source.Services)) for j := 0; j < len(source.Services); j++ { - hcloudLoadBalancerServiceList[j] = c.LoadBalancerServiceFromSchema(source.Services[j]) + hcloudLoadBalancer.Services[j] = c.LoadBalancerServiceFromSchema(source.Services[j]) } } - hcloudLoadBalancer.Services = hcloudLoadBalancerServiceList - var hcloudLoadBalancerTargetList []LoadBalancerTarget if source.Targets != nil { - hcloudLoadBalancerTargetList = make([]LoadBalancerTarget, len(source.Targets)) + hcloudLoadBalancer.Targets = make([]LoadBalancerTarget, len(source.Targets)) for k := 0; k < len(source.Targets); k++ { - hcloudLoadBalancerTargetList[k] = c.LoadBalancerTargetFromSchema(source.Targets[k]) + hcloudLoadBalancer.Targets[k] = c.LoadBalancerTargetFromSchema(source.Targets[k]) } } - hcloudLoadBalancer.Targets = hcloudLoadBalancerTargetList hcloudLoadBalancer.Protection = c.schemaLoadBalancerProtectionToHcloudLoadBalancerProtection(source.Protection) hcloudLoadBalancer.Labels = source.Labels hcloudLoadBalancer.Created = c.timeTimeToTimeTime(source.Created) hcloudLoadBalancer.IncludedTraffic = source.IncludedTraffic - var xuint64 uint64 if source.OutgoingTraffic != nil { - xuint64 = *source.OutgoingTraffic + hcloudLoadBalancer.OutgoingTraffic = *source.OutgoingTraffic } - hcloudLoadBalancer.OutgoingTraffic = xuint64 - var xuint642 uint64 if source.IngoingTraffic != nil { - xuint642 = *source.IngoingTraffic + hcloudLoadBalancer.IngoingTraffic = *source.IngoingTraffic } - hcloudLoadBalancer.IngoingTraffic = xuint642 return &hcloudLoadBalancer } func (c *converterImpl) LoadBalancerMetricsFromSchema(source *schema.LoadBalancerGetMetricsResponse) (*LoadBalancerMetrics, error) { @@ -254,18 +220,16 @@ func (c *converterImpl) LoadBalancerMetricsFromSchema(source *schema.LoadBalance hcloudLoadBalancerMetrics.Start = c.timeTimeToTimeTime((*source).Metrics.Start) hcloudLoadBalancerMetrics.End = c.timeTimeToTimeTime((*source).Metrics.End) hcloudLoadBalancerMetrics.Step = (*source).Metrics.Step - var mapStringHcloudLoadBalancerMetricsValueList map[string][]LoadBalancerMetricsValue if (*source).Metrics.TimeSeries != nil { - mapStringHcloudLoadBalancerMetricsValueList = make(map[string][]LoadBalancerMetricsValue, len((*source).Metrics.TimeSeries)) + hcloudLoadBalancerMetrics.TimeSeries = make(map[string][]LoadBalancerMetricsValue, len((*source).Metrics.TimeSeries)) for key, value := range (*source).Metrics.TimeSeries { hcloudLoadBalancerMetricsValueList, err := loadBalancerMetricsTimeSeriesFromSchema(value) if err != nil { return nil, err } - mapStringHcloudLoadBalancerMetricsValueList[key] = hcloudLoadBalancerMetricsValueList + hcloudLoadBalancerMetrics.TimeSeries[key] = hcloudLoadBalancerMetricsValueList } } - hcloudLoadBalancerMetrics.TimeSeries = mapStringHcloudLoadBalancerMetricsValueList pHcloudLoadBalancerMetrics = &hcloudLoadBalancerMetrics } return pHcloudLoadBalancerMetrics, nil @@ -300,22 +264,18 @@ func (c *converterImpl) LoadBalancerTargetFromSchema(source schema.LoadBalancerT hcloudLoadBalancerTarget.Server = c.pSchemaLoadBalancerTargetServerToPHcloudLoadBalancerTargetServer(source.Server) hcloudLoadBalancerTarget.LabelSelector = c.pSchemaLoadBalancerTargetLabelSelectorToPHcloudLoadBalancerTargetLabelSelector(source.LabelSelector) hcloudLoadBalancerTarget.IP = c.pSchemaLoadBalancerTargetIPToPHcloudLoadBalancerTargetIP(source.IP) - var hcloudLoadBalancerTargetHealthStatusList []LoadBalancerTargetHealthStatus if source.HealthStatus != nil { - hcloudLoadBalancerTargetHealthStatusList = make([]LoadBalancerTargetHealthStatus, len(source.HealthStatus)) + hcloudLoadBalancerTarget.HealthStatus = make([]LoadBalancerTargetHealthStatus, len(source.HealthStatus)) for i := 0; i < len(source.HealthStatus); i++ { - hcloudLoadBalancerTargetHealthStatusList[i] = c.LoadBalancerTargetHealthStatusFromSchema(source.HealthStatus[i]) + hcloudLoadBalancerTarget.HealthStatus[i] = c.LoadBalancerTargetHealthStatusFromSchema(source.HealthStatus[i]) } } - hcloudLoadBalancerTarget.HealthStatus = hcloudLoadBalancerTargetHealthStatusList - var hcloudLoadBalancerTargetList []LoadBalancerTarget if source.Targets != nil { - hcloudLoadBalancerTargetList = make([]LoadBalancerTarget, len(source.Targets)) + hcloudLoadBalancerTarget.Targets = make([]LoadBalancerTarget, len(source.Targets)) for j := 0; j < len(source.Targets); j++ { - hcloudLoadBalancerTargetList[j] = c.LoadBalancerTargetFromSchema(source.Targets[j]) + hcloudLoadBalancerTarget.Targets[j] = c.LoadBalancerTargetFromSchema(source.Targets[j]) } } - hcloudLoadBalancerTarget.Targets = hcloudLoadBalancerTargetList hcloudLoadBalancerTarget.UsePrivateIP = source.UsePrivateIP return hcloudLoadBalancerTarget } @@ -340,14 +300,12 @@ func (c *converterImpl) LoadBalancerTypeFromSchema(source schema.LoadBalancerTyp hcloudLoadBalancerType.MaxServices = source.MaxServices hcloudLoadBalancerType.MaxTargets = source.MaxTargets hcloudLoadBalancerType.MaxAssignedCertificates = source.MaxAssignedCertificates - var hcloudLoadBalancerTypeLocationPricingList []LoadBalancerTypeLocationPricing if source.Prices != nil { - hcloudLoadBalancerTypeLocationPricingList = make([]LoadBalancerTypeLocationPricing, len(source.Prices)) + hcloudLoadBalancerType.Pricings = make([]LoadBalancerTypeLocationPricing, len(source.Prices)) for i := 0; i < len(source.Prices); i++ { - hcloudLoadBalancerTypeLocationPricingList[i] = c.LoadBalancerTypeLocationPricingFromSchema(source.Prices[i]) + hcloudLoadBalancerType.Pricings[i] = c.LoadBalancerTypeLocationPricingFromSchema(source.Prices[i]) } } - hcloudLoadBalancerType.Pricings = hcloudLoadBalancerTypeLocationPricingList hcloudLoadBalancerType.Deprecated = source.Deprecated return &hcloudLoadBalancerType } @@ -357,6 +315,8 @@ func (c *converterImpl) LoadBalancerTypeLocationPricingFromSchema(source schema. hcloudLoadBalancerTypeLocationPricing.Location = &hcloudLocation hcloudLoadBalancerTypeLocationPricing.Hourly = c.PriceFromSchema(source.PriceHourly) hcloudLoadBalancerTypeLocationPricing.Monthly = c.PriceFromSchema(source.PriceMonthly) + hcloudLoadBalancerTypeLocationPricing.IncludedTraffic = source.IncludedTraffic + hcloudLoadBalancerTypeLocationPricing.PerTBTraffic = c.PriceFromSchema(source.PricePerTBTraffic) return hcloudLoadBalancerTypeLocationPricing } func (c *converterImpl) LocationFromSchema(source schema.Location) *Location { @@ -378,31 +338,32 @@ func (c *converterImpl) NetworkFromSchema(source schema.Network) *Network { hcloudNetwork.Created = c.timeTimeToTimeTime(source.Created) netIPNet := ipNetFromString(source.IPRange) hcloudNetwork.IPRange = &netIPNet - var hcloudNetworkSubnetList []NetworkSubnet if source.Subnets != nil { - hcloudNetworkSubnetList = make([]NetworkSubnet, len(source.Subnets)) + hcloudNetwork.Subnets = make([]NetworkSubnet, len(source.Subnets)) for i := 0; i < len(source.Subnets); i++ { - hcloudNetworkSubnetList[i] = c.NetworkSubnetFromSchema(source.Subnets[i]) + hcloudNetwork.Subnets[i] = c.NetworkSubnetFromSchema(source.Subnets[i]) } } - hcloudNetwork.Subnets = hcloudNetworkSubnetList - var hcloudNetworkRouteList []NetworkRoute if source.Routes != nil { - hcloudNetworkRouteList = make([]NetworkRoute, len(source.Routes)) + hcloudNetwork.Routes = make([]NetworkRoute, len(source.Routes)) for j := 0; j < len(source.Routes); j++ { - hcloudNetworkRouteList[j] = c.NetworkRouteFromSchema(source.Routes[j]) + hcloudNetwork.Routes[j] = c.NetworkRouteFromSchema(source.Routes[j]) } } - hcloudNetwork.Routes = hcloudNetworkRouteList - var pHcloudServerList []*Server if source.Servers != nil { - pHcloudServerList = make([]*Server, len(source.Servers)) + hcloudNetwork.Servers = make([]*Server, len(source.Servers)) for k := 0; k < len(source.Servers); k++ { hcloudServer := serverFromInt64(source.Servers[k]) - pHcloudServerList[k] = &hcloudServer + hcloudNetwork.Servers[k] = &hcloudServer + } + } + if source.LoadBalancers != nil { + hcloudNetwork.LoadBalancers = make([]*LoadBalancer, len(source.LoadBalancers)) + for l := 0; l < len(source.LoadBalancers); l++ { + hcloudLoadBalancer := loadBalancerFromInt64(source.LoadBalancers[l]) + hcloudNetwork.LoadBalancers[l] = &hcloudLoadBalancer } } - hcloudNetwork.Servers = pHcloudServerList hcloudNetwork.Protection = c.schemaNetworkProtectionToHcloudNetworkProtection(source.Protection) hcloudNetwork.Labels = source.Labels hcloudNetwork.ExposeRoutesToVSwitch = source.ExposeRoutesToVSwitch @@ -474,11 +435,9 @@ func (c *converterImpl) PrimaryIPFromSchema(source schema.PrimaryIP) *PrimaryIP hcloudPrimaryIP.Type = PrimaryIPType(source.Type) hcloudPrimaryIP.Protection = c.schemaPrimaryIPProtectionToHcloudPrimaryIPProtection(source.Protection) hcloudPrimaryIP.DNSPtr = mapFromPrimaryIPDNSPtrSchema(source.DNSPtr) - var xint64 int64 if source.AssigneeID != nil { - xint64 = *source.AssigneeID + hcloudPrimaryIP.AssigneeID = *source.AssigneeID } - hcloudPrimaryIP.AssigneeID = xint64 hcloudPrimaryIP.AssigneeType = source.AssigneeType hcloudPrimaryIP.AutoDelete = source.AutoDelete hcloudPrimaryIP.Blocked = source.Blocked @@ -507,14 +466,12 @@ func (c *converterImpl) SchemaFromAction(source *Action) schema.Action { schemaAction2.Started = c.timeTimeToTimeTime((*source).Started) schemaAction2.Finished = timeToTimePtr((*source).Finished) schemaAction2.Error = schemaActionErrorFromAction((*source)) - var schemaActionResourceReferenceList []schema.ActionResourceReference if (*source).Resources != nil { - schemaActionResourceReferenceList = make([]schema.ActionResourceReference, len((*source).Resources)) + schemaAction2.Resources = make([]schema.ActionResourceReference, len((*source).Resources)) for i := 0; i < len((*source).Resources); i++ { - schemaActionResourceReferenceList[i] = c.pHcloudActionResourceToSchemaActionResourceReference((*source).Resources[i]) + schemaAction2.Resources[i] = c.pHcloudActionResourceToSchemaActionResourceReference((*source).Resources[i]) } } - schemaAction2.Resources = schemaActionResourceReferenceList schemaAction = schemaAction2 } return schemaAction @@ -544,14 +501,12 @@ func (c *converterImpl) SchemaFromCertificate(source *Certificate) schema.Certif schemaCertificate2.DomainNames = (*source).DomainNames schemaCertificate2.Fingerprint = (*source).Fingerprint schemaCertificate2.Status = c.pHcloudCertificateStatusToPSchemaCertificateStatusRef((*source).Status) - var schemaCertificateUsedByRefList []schema.CertificateUsedByRef if (*source).UsedBy != nil { - schemaCertificateUsedByRefList = make([]schema.CertificateUsedByRef, len((*source).UsedBy)) + schemaCertificate2.UsedBy = make([]schema.CertificateUsedByRef, len((*source).UsedBy)) for i := 0; i < len((*source).UsedBy); i++ { - schemaCertificateUsedByRefList[i] = c.hcloudCertificateUsedByRefToSchemaCertificateUsedByRef((*source).UsedBy[i]) + schemaCertificate2.UsedBy[i] = c.hcloudCertificateUsedByRefToSchemaCertificateUsedByRef((*source).UsedBy[i]) } } - schemaCertificate2.UsedBy = schemaCertificateUsedByRefList schemaCertificate = schemaCertificate2 } return schemaCertificate @@ -595,22 +550,18 @@ func (c *converterImpl) SchemaFromFirewall(source *Firewall) schema.Firewall { schemaFirewall2.Name = (*source).Name schemaFirewall2.Labels = (*source).Labels schemaFirewall2.Created = c.timeTimeToTimeTime((*source).Created) - var schemaFirewallRuleList []schema.FirewallRule if (*source).Rules != nil { - schemaFirewallRuleList = make([]schema.FirewallRule, len((*source).Rules)) + schemaFirewall2.Rules = make([]schema.FirewallRule, len((*source).Rules)) for i := 0; i < len((*source).Rules); i++ { - schemaFirewallRuleList[i] = c.hcloudFirewallRuleToSchemaFirewallRule((*source).Rules[i]) + schemaFirewall2.Rules[i] = c.hcloudFirewallRuleToSchemaFirewallRule((*source).Rules[i]) } } - schemaFirewall2.Rules = schemaFirewallRuleList - var schemaFirewallResourceList []schema.FirewallResource if (*source).AppliedTo != nil { - schemaFirewallResourceList = make([]schema.FirewallResource, len((*source).AppliedTo)) + schemaFirewall2.AppliedTo = make([]schema.FirewallResource, len((*source).AppliedTo)) for j := 0; j < len((*source).AppliedTo); j++ { - schemaFirewallResourceList[j] = c.SchemaFromFirewallResource((*source).AppliedTo[j]) + schemaFirewall2.AppliedTo[j] = c.SchemaFromFirewallResource((*source).AppliedTo[j]) } } - schemaFirewall2.AppliedTo = schemaFirewallResourceList schemaFirewall = schemaFirewall2 } return schemaFirewall @@ -619,22 +570,18 @@ func (c *converterImpl) SchemaFromFirewallCreateOpts(source FirewallCreateOpts) var schemaFirewallCreateRequest schema.FirewallCreateRequest schemaFirewallCreateRequest.Name = source.Name schemaFirewallCreateRequest.Labels = stringMapToStringMapPtr(source.Labels) - var schemaFirewallRuleRequestList []schema.FirewallRuleRequest if source.Rules != nil { - schemaFirewallRuleRequestList = make([]schema.FirewallRuleRequest, len(source.Rules)) + schemaFirewallCreateRequest.Rules = make([]schema.FirewallRuleRequest, len(source.Rules)) for i := 0; i < len(source.Rules); i++ { - schemaFirewallRuleRequestList[i] = c.hcloudFirewallRuleToSchemaFirewallRuleRequest(source.Rules[i]) + schemaFirewallCreateRequest.Rules[i] = c.hcloudFirewallRuleToSchemaFirewallRuleRequest(source.Rules[i]) } } - schemaFirewallCreateRequest.Rules = schemaFirewallRuleRequestList - var schemaFirewallResourceList []schema.FirewallResource if source.ApplyTo != nil { - schemaFirewallResourceList = make([]schema.FirewallResource, len(source.ApplyTo)) + schemaFirewallCreateRequest.ApplyTo = make([]schema.FirewallResource, len(source.ApplyTo)) for j := 0; j < len(source.ApplyTo); j++ { - schemaFirewallResourceList[j] = c.SchemaFromFirewallResource(source.ApplyTo[j]) + schemaFirewallCreateRequest.ApplyTo[j] = c.SchemaFromFirewallResource(source.ApplyTo[j]) } } - schemaFirewallCreateRequest.ApplyTo = schemaFirewallResourceList return schemaFirewallCreateRequest } func (c *converterImpl) SchemaFromFirewallResource(source FirewallResource) schema.FirewallResource { @@ -646,14 +593,12 @@ func (c *converterImpl) SchemaFromFirewallResource(source FirewallResource) sche } func (c *converterImpl) SchemaFromFirewallSetRulesOpts(source FirewallSetRulesOpts) schema.FirewallActionSetRulesRequest { var schemaFirewallActionSetRulesRequest schema.FirewallActionSetRulesRequest - var schemaFirewallRuleRequestList []schema.FirewallRuleRequest if source.Rules != nil { - schemaFirewallRuleRequestList = make([]schema.FirewallRuleRequest, len(source.Rules)) + schemaFirewallActionSetRulesRequest.Rules = make([]schema.FirewallRuleRequest, len(source.Rules)) for i := 0; i < len(source.Rules); i++ { - schemaFirewallRuleRequestList[i] = c.hcloudFirewallRuleToSchemaFirewallRuleRequest(source.Rules[i]) + schemaFirewallActionSetRulesRequest.Rules[i] = c.hcloudFirewallRuleToSchemaFirewallRuleRequest(source.Rules[i]) } } - schemaFirewallActionSetRulesRequest.Rules = schemaFirewallRuleRequestList return schemaFirewallActionSetRulesRequest } func (c *converterImpl) SchemaFromFloatingIP(source *FloatingIP) schema.FloatingIP { @@ -685,12 +630,10 @@ func (c *converterImpl) SchemaFromISO(source *ISO) schema.ISO { schemaISO2.Name = (*source).Name schemaISO2.Description = (*source).Description schemaISO2.Type = string((*source).Type) - var pString *string if (*source).Architecture != nil { xstring := string(*(*source).Architecture) - pString = &xstring + schemaISO2.Architecture = &xstring } - schemaISO2.Architecture = pString schemaISO2.DeprecatableResource = c.hcloudDeprecatableResourceToSchemaDeprecatableResource((*source).DeprecatableResource) schemaISO = schemaISO2 } @@ -710,35 +653,29 @@ func (c *converterImpl) SchemaFromLoadBalancer(source *LoadBalancer) schema.Load schemaLoadBalancer2.ID = (*source).ID schemaLoadBalancer2.Name = (*source).Name schemaLoadBalancer2.PublicNet = c.hcloudLoadBalancerPublicNetToSchemaLoadBalancerPublicNet((*source).PublicNet) - var schemaLoadBalancerPrivateNetList []schema.LoadBalancerPrivateNet if (*source).PrivateNet != nil { - schemaLoadBalancerPrivateNetList = make([]schema.LoadBalancerPrivateNet, len((*source).PrivateNet)) + schemaLoadBalancer2.PrivateNet = make([]schema.LoadBalancerPrivateNet, len((*source).PrivateNet)) for i := 0; i < len((*source).PrivateNet); i++ { - schemaLoadBalancerPrivateNetList[i] = c.hcloudLoadBalancerPrivateNetToSchemaLoadBalancerPrivateNet((*source).PrivateNet[i]) + schemaLoadBalancer2.PrivateNet[i] = c.hcloudLoadBalancerPrivateNetToSchemaLoadBalancerPrivateNet((*source).PrivateNet[i]) } } - schemaLoadBalancer2.PrivateNet = schemaLoadBalancerPrivateNetList schemaLoadBalancer2.Location = c.SchemaFromLocation((*source).Location) schemaLoadBalancer2.LoadBalancerType = c.SchemaFromLoadBalancerType((*source).LoadBalancerType) schemaLoadBalancer2.Protection = c.hcloudLoadBalancerProtectionToSchemaLoadBalancerProtection((*source).Protection) schemaLoadBalancer2.Labels = (*source).Labels schemaLoadBalancer2.Created = c.timeTimeToTimeTime((*source).Created) - var schemaLoadBalancerServiceList []schema.LoadBalancerService if (*source).Services != nil { - schemaLoadBalancerServiceList = make([]schema.LoadBalancerService, len((*source).Services)) + schemaLoadBalancer2.Services = make([]schema.LoadBalancerService, len((*source).Services)) for j := 0; j < len((*source).Services); j++ { - schemaLoadBalancerServiceList[j] = c.SchemaFromLoadBalancerService((*source).Services[j]) + schemaLoadBalancer2.Services[j] = c.SchemaFromLoadBalancerService((*source).Services[j]) } } - schemaLoadBalancer2.Services = schemaLoadBalancerServiceList - var schemaLoadBalancerTargetList []schema.LoadBalancerTarget if (*source).Targets != nil { - schemaLoadBalancerTargetList = make([]schema.LoadBalancerTarget, len((*source).Targets)) + schemaLoadBalancer2.Targets = make([]schema.LoadBalancerTarget, len((*source).Targets)) for k := 0; k < len((*source).Targets); k++ { - schemaLoadBalancerTargetList[k] = c.SchemaFromLoadBalancerTarget((*source).Targets[k]) + schemaLoadBalancer2.Targets[k] = c.SchemaFromLoadBalancerTarget((*source).Targets[k]) } } - schemaLoadBalancer2.Targets = schemaLoadBalancerTargetList schemaLoadBalancer2.Algorithm = c.hcloudLoadBalancerAlgorithmToSchemaLoadBalancerAlgorithm((*source).Algorithm) schemaLoadBalancer2.IncludedTraffic = (*source).IncludedTraffic schemaLoadBalancer2.OutgoingTraffic = mapZeroUint64ToNil((*source).OutgoingTraffic) @@ -760,27 +697,23 @@ func (c *converterImpl) SchemaFromLoadBalancerAddServiceOpts(source LoadBalancer func (c *converterImpl) SchemaFromLoadBalancerCreateOpts(source LoadBalancerCreateOpts) schema.LoadBalancerCreateRequest { var schemaLoadBalancerCreateRequest schema.LoadBalancerCreateRequest schemaLoadBalancerCreateRequest.Name = source.Name - schemaLoadBalancerCreateRequest.LoadBalancerType = anyFromLoadBalancerType(source.LoadBalancerType) + schemaLoadBalancerCreateRequest.LoadBalancerType = c.pHcloudLoadBalancerTypeToSchemaIDOrName(source.LoadBalancerType) schemaLoadBalancerCreateRequest.Algorithm = c.pHcloudLoadBalancerAlgorithmToPSchemaLoadBalancerCreateRequestAlgorithm(source.Algorithm) schemaLoadBalancerCreateRequest.Location = c.pHcloudLocationToPString(source.Location) schemaLoadBalancerCreateRequest.NetworkZone = stringPtrFromNetworkZone(source.NetworkZone) schemaLoadBalancerCreateRequest.Labels = stringMapToStringMapPtr(source.Labels) - var schemaLoadBalancerCreateRequestTargetList []schema.LoadBalancerCreateRequestTarget if source.Targets != nil { - schemaLoadBalancerCreateRequestTargetList = make([]schema.LoadBalancerCreateRequestTarget, len(source.Targets)) + schemaLoadBalancerCreateRequest.Targets = make([]schema.LoadBalancerCreateRequestTarget, len(source.Targets)) for i := 0; i < len(source.Targets); i++ { - schemaLoadBalancerCreateRequestTargetList[i] = c.hcloudLoadBalancerCreateOptsTargetToSchemaLoadBalancerCreateRequestTarget(source.Targets[i]) + schemaLoadBalancerCreateRequest.Targets[i] = c.hcloudLoadBalancerCreateOptsTargetToSchemaLoadBalancerCreateRequestTarget(source.Targets[i]) } } - schemaLoadBalancerCreateRequest.Targets = schemaLoadBalancerCreateRequestTargetList - var schemaLoadBalancerCreateRequestServiceList []schema.LoadBalancerCreateRequestService if source.Services != nil { - schemaLoadBalancerCreateRequestServiceList = make([]schema.LoadBalancerCreateRequestService, len(source.Services)) + schemaLoadBalancerCreateRequest.Services = make([]schema.LoadBalancerCreateRequestService, len(source.Services)) for j := 0; j < len(source.Services); j++ { - schemaLoadBalancerCreateRequestServiceList[j] = c.hcloudLoadBalancerCreateOptsServiceToSchemaLoadBalancerCreateRequestService(source.Services[j]) + schemaLoadBalancerCreateRequest.Services[j] = c.hcloudLoadBalancerCreateOptsServiceToSchemaLoadBalancerCreateRequestService(source.Services[j]) } } - schemaLoadBalancerCreateRequest.Services = schemaLoadBalancerCreateRequestServiceList schemaLoadBalancerCreateRequest.PublicInterface = source.PublicInterface schemaLoadBalancerCreateRequest.Network = c.pHcloudNetworkToPInt64(source.Network) return schemaLoadBalancerCreateRequest @@ -791,11 +724,9 @@ func (c *converterImpl) SchemaFromLoadBalancerCreateOptsTargetServer(source Load if source.Server != nil { pInt64 = &source.Server.ID } - var xint64 int64 if pInt64 != nil { - xint64 = *pInt64 + schemaLoadBalancerCreateRequestTargetServer.ID = *pInt64 } - schemaLoadBalancerCreateRequestTargetServer.ID = xint64 return schemaLoadBalancerCreateRequestTargetServer } func (c *converterImpl) SchemaFromLoadBalancerServerTarget(source LoadBalancerTargetServer) schema.LoadBalancerTargetServer { @@ -829,23 +760,19 @@ func (c *converterImpl) SchemaFromLoadBalancerTarget(source LoadBalancerTarget) schemaLoadBalancerTarget.Server = c.pHcloudLoadBalancerTargetServerToPSchemaLoadBalancerTargetServer(source.Server) schemaLoadBalancerTarget.LabelSelector = c.pHcloudLoadBalancerTargetLabelSelectorToPSchemaLoadBalancerTargetLabelSelector(source.LabelSelector) schemaLoadBalancerTarget.IP = c.pHcloudLoadBalancerTargetIPToPSchemaLoadBalancerTargetIP(source.IP) - var schemaLoadBalancerTargetHealthStatusList []schema.LoadBalancerTargetHealthStatus if source.HealthStatus != nil { - schemaLoadBalancerTargetHealthStatusList = make([]schema.LoadBalancerTargetHealthStatus, len(source.HealthStatus)) + schemaLoadBalancerTarget.HealthStatus = make([]schema.LoadBalancerTargetHealthStatus, len(source.HealthStatus)) for i := 0; i < len(source.HealthStatus); i++ { - schemaLoadBalancerTargetHealthStatusList[i] = c.SchemaFromLoadBalancerTargetHealthStatus(source.HealthStatus[i]) + schemaLoadBalancerTarget.HealthStatus[i] = c.SchemaFromLoadBalancerTargetHealthStatus(source.HealthStatus[i]) } } - schemaLoadBalancerTarget.HealthStatus = schemaLoadBalancerTargetHealthStatusList schemaLoadBalancerTarget.UsePrivateIP = source.UsePrivateIP - var schemaLoadBalancerTargetList []schema.LoadBalancerTarget if source.Targets != nil { - schemaLoadBalancerTargetList = make([]schema.LoadBalancerTarget, len(source.Targets)) + schemaLoadBalancerTarget.Targets = make([]schema.LoadBalancerTarget, len(source.Targets)) for j := 0; j < len(source.Targets); j++ { - schemaLoadBalancerTargetList[j] = c.SchemaFromLoadBalancerTarget(source.Targets[j]) + schemaLoadBalancerTarget.Targets[j] = c.SchemaFromLoadBalancerTarget(source.Targets[j]) } } - schemaLoadBalancerTarget.Targets = schemaLoadBalancerTargetList return schemaLoadBalancerTarget } func (c *converterImpl) SchemaFromLoadBalancerTargetHealthStatus(source LoadBalancerTargetHealthStatus) schema.LoadBalancerTargetHealthStatus { @@ -865,14 +792,12 @@ func (c *converterImpl) SchemaFromLoadBalancerType(source *LoadBalancerType) sch schemaLoadBalancerType2.MaxServices = (*source).MaxServices schemaLoadBalancerType2.MaxTargets = (*source).MaxTargets schemaLoadBalancerType2.MaxAssignedCertificates = (*source).MaxAssignedCertificates - var schemaPricingLoadBalancerTypePriceList []schema.PricingLoadBalancerTypePrice if (*source).Pricings != nil { - schemaPricingLoadBalancerTypePriceList = make([]schema.PricingLoadBalancerTypePrice, len((*source).Pricings)) + schemaLoadBalancerType2.Prices = make([]schema.PricingLoadBalancerTypePrice, len((*source).Pricings)) for i := 0; i < len((*source).Pricings); i++ { - schemaPricingLoadBalancerTypePriceList[i] = c.SchemaFromLoadBalancerTypeLocationPricing((*source).Pricings[i]) + schemaLoadBalancerType2.Prices[i] = c.SchemaFromLoadBalancerTypeLocationPricing((*source).Pricings[i]) } } - schemaLoadBalancerType2.Prices = schemaPricingLoadBalancerTypePriceList schemaLoadBalancerType2.Deprecated = (*source).Deprecated schemaLoadBalancerType = schemaLoadBalancerType2 } @@ -883,6 +808,8 @@ func (c *converterImpl) SchemaFromLoadBalancerTypeLocationPricing(source LoadBal schemaPricingLoadBalancerTypePrice.Location = c.pHcloudLocationToString(source.Location) schemaPricingLoadBalancerTypePrice.PriceHourly = c.hcloudPriceToSchemaPrice(source.Hourly) schemaPricingLoadBalancerTypePrice.PriceMonthly = c.hcloudPriceToSchemaPrice(source.Monthly) + schemaPricingLoadBalancerTypePrice.IncludedTraffic = source.IncludedTraffic + schemaPricingLoadBalancerTypePrice.PricePerTBTraffic = c.hcloudPriceToSchemaPrice(source.PerTBTraffic) return schemaPricingLoadBalancerTypePrice } func (c *converterImpl) SchemaFromLoadBalancerUpdateServiceOpts(source LoadBalancerUpdateServiceOpts) schema.LoadBalancerActionUpdateServiceRequest { @@ -918,30 +845,30 @@ func (c *converterImpl) SchemaFromNetwork(source *Network) schema.Network { schemaNetwork2.Name = (*source).Name schemaNetwork2.Created = c.timeTimeToTimeTime((*source).Created) schemaNetwork2.IPRange = c.pNetIPNetToString((*source).IPRange) - var schemaNetworkSubnetList []schema.NetworkSubnet if (*source).Subnets != nil { - schemaNetworkSubnetList = make([]schema.NetworkSubnet, len((*source).Subnets)) + schemaNetwork2.Subnets = make([]schema.NetworkSubnet, len((*source).Subnets)) for i := 0; i < len((*source).Subnets); i++ { - schemaNetworkSubnetList[i] = c.SchemaFromNetworkSubnet((*source).Subnets[i]) + schemaNetwork2.Subnets[i] = c.SchemaFromNetworkSubnet((*source).Subnets[i]) } } - schemaNetwork2.Subnets = schemaNetworkSubnetList - var schemaNetworkRouteList []schema.NetworkRoute if (*source).Routes != nil { - schemaNetworkRouteList = make([]schema.NetworkRoute, len((*source).Routes)) + schemaNetwork2.Routes = make([]schema.NetworkRoute, len((*source).Routes)) for j := 0; j < len((*source).Routes); j++ { - schemaNetworkRouteList[j] = c.SchemaFromNetworkRoute((*source).Routes[j]) + schemaNetwork2.Routes[j] = c.SchemaFromNetworkRoute((*source).Routes[j]) } } - schemaNetwork2.Routes = schemaNetworkRouteList - var int64List []int64 if (*source).Servers != nil { - int64List = make([]int64, len((*source).Servers)) + schemaNetwork2.Servers = make([]int64, len((*source).Servers)) for k := 0; k < len((*source).Servers); k++ { - int64List[k] = c.pHcloudServerToInt64((*source).Servers[k]) + schemaNetwork2.Servers[k] = c.pHcloudServerToInt64((*source).Servers[k]) + } + } + if (*source).LoadBalancers != nil { + schemaNetwork2.LoadBalancers = make([]int64, len((*source).LoadBalancers)) + for l := 0; l < len((*source).LoadBalancers); l++ { + schemaNetwork2.LoadBalancers[l] = c.pHcloudLoadBalancerToInt64((*source).LoadBalancers[l]) } } - schemaNetwork2.Servers = int64List schemaNetwork2.Protection = c.hcloudNetworkProtectionToSchemaNetworkProtection((*source).Protection) schemaNetwork2.Labels = (*source).Labels schemaNetwork2.ExposeRoutesToVSwitch = (*source).ExposeRoutesToVSwitch @@ -1001,40 +928,32 @@ func (c *converterImpl) SchemaFromPricing(source Pricing) schema.Pricing { schemaPricing.VATRate = source.Image.PerGBMonth.VATRate schemaPricing.Image = c.schemaFromImagePricing(source.Image) schemaPricing.FloatingIP = c.schemaFromFloatingIPPricing(source.FloatingIP) - var schemaPricingFloatingIPTypeList []schema.PricingFloatingIPType if source.FloatingIPs != nil { - schemaPricingFloatingIPTypeList = make([]schema.PricingFloatingIPType, len(source.FloatingIPs)) + schemaPricing.FloatingIPs = make([]schema.PricingFloatingIPType, len(source.FloatingIPs)) for i := 0; i < len(source.FloatingIPs); i++ { - schemaPricingFloatingIPTypeList[i] = c.schemaFromFloatingIPTypePricing(source.FloatingIPs[i]) + schemaPricing.FloatingIPs[i] = c.schemaFromFloatingIPTypePricing(source.FloatingIPs[i]) } } - schemaPricing.FloatingIPs = schemaPricingFloatingIPTypeList - var schemaPricingPrimaryIPList []schema.PricingPrimaryIP if source.PrimaryIPs != nil { - schemaPricingPrimaryIPList = make([]schema.PricingPrimaryIP, len(source.PrimaryIPs)) + schemaPricing.PrimaryIPs = make([]schema.PricingPrimaryIP, len(source.PrimaryIPs)) for j := 0; j < len(source.PrimaryIPs); j++ { - schemaPricingPrimaryIPList[j] = c.schemaFromPrimaryIPPricing(source.PrimaryIPs[j]) + schemaPricing.PrimaryIPs[j] = c.schemaFromPrimaryIPPricing(source.PrimaryIPs[j]) } } - schemaPricing.PrimaryIPs = schemaPricingPrimaryIPList schemaPricing.Traffic = c.schemaFromTrafficPricing(source.Traffic) schemaPricing.ServerBackup = c.hcloudServerBackupPricingToSchemaPricingServerBackup(source.ServerBackup) - var schemaPricingServerTypeList []schema.PricingServerType if source.ServerTypes != nil { - schemaPricingServerTypeList = make([]schema.PricingServerType, len(source.ServerTypes)) + schemaPricing.ServerTypes = make([]schema.PricingServerType, len(source.ServerTypes)) for k := 0; k < len(source.ServerTypes); k++ { - schemaPricingServerTypeList[k] = c.schemaFromServerTypePricing(source.ServerTypes[k]) + schemaPricing.ServerTypes[k] = c.schemaFromServerTypePricing(source.ServerTypes[k]) } } - schemaPricing.ServerTypes = schemaPricingServerTypeList - var schemaPricingLoadBalancerTypeList []schema.PricingLoadBalancerType if source.LoadBalancerTypes != nil { - schemaPricingLoadBalancerTypeList = make([]schema.PricingLoadBalancerType, len(source.LoadBalancerTypes)) + schemaPricing.LoadBalancerTypes = make([]schema.PricingLoadBalancerType, len(source.LoadBalancerTypes)) for l := 0; l < len(source.LoadBalancerTypes); l++ { - schemaPricingLoadBalancerTypeList[l] = c.schemaFromLoadBalancerTypePricing(source.LoadBalancerTypes[l]) + schemaPricing.LoadBalancerTypes[l] = c.schemaFromLoadBalancerTypePricing(source.LoadBalancerTypes[l]) } } - schemaPricing.LoadBalancerTypes = schemaPricingLoadBalancerTypeList schemaPricing.Volume = c.schemaFromVolumePricing(source.Volume) return schemaPricing } @@ -1059,6 +978,44 @@ func (c *converterImpl) SchemaFromPrimaryIP(source *PrimaryIP) schema.PrimaryIP } return schemaPrimaryIP } +func (c *converterImpl) SchemaFromPrimaryIPAssignOpts(source PrimaryIPAssignOpts) schema.PrimaryIPActionAssignRequest { + var schemaPrimaryIPActionAssignRequest schema.PrimaryIPActionAssignRequest + schemaPrimaryIPActionAssignRequest.AssigneeID = source.AssigneeID + schemaPrimaryIPActionAssignRequest.AssigneeType = source.AssigneeType + return schemaPrimaryIPActionAssignRequest +} +func (c *converterImpl) SchemaFromPrimaryIPChangeDNSPtrOpts(source PrimaryIPChangeDNSPtrOpts) schema.PrimaryIPActionChangeDNSPtrRequest { + var schemaPrimaryIPActionChangeDNSPtrRequest schema.PrimaryIPActionChangeDNSPtrRequest + schemaPrimaryIPActionChangeDNSPtrRequest.IP = source.IP + pString := source.DNSPtr + schemaPrimaryIPActionChangeDNSPtrRequest.DNSPtr = &pString + return schemaPrimaryIPActionChangeDNSPtrRequest +} +func (c *converterImpl) SchemaFromPrimaryIPChangeProtectionOpts(source PrimaryIPChangeProtectionOpts) schema.PrimaryIPActionChangeProtectionRequest { + var schemaPrimaryIPActionChangeProtectionRequest schema.PrimaryIPActionChangeProtectionRequest + schemaPrimaryIPActionChangeProtectionRequest.Delete = source.Delete + return schemaPrimaryIPActionChangeProtectionRequest +} +func (c *converterImpl) SchemaFromPrimaryIPCreateOpts(source PrimaryIPCreateOpts) schema.PrimaryIPCreateRequest { + var schemaPrimaryIPCreateRequest schema.PrimaryIPCreateRequest + schemaPrimaryIPCreateRequest.Name = source.Name + schemaPrimaryIPCreateRequest.Type = string(source.Type) + schemaPrimaryIPCreateRequest.AssigneeType = source.AssigneeType + schemaPrimaryIPCreateRequest.AssigneeID = source.AssigneeID + schemaPrimaryIPCreateRequest.Labels = source.Labels + schemaPrimaryIPCreateRequest.AutoDelete = source.AutoDelete + schemaPrimaryIPCreateRequest.Datacenter = source.Datacenter + return schemaPrimaryIPCreateRequest +} +func (c *converterImpl) SchemaFromPrimaryIPUpdateOpts(source PrimaryIPUpdateOpts) schema.PrimaryIPUpdateRequest { + var schemaPrimaryIPUpdateRequest schema.PrimaryIPUpdateRequest + schemaPrimaryIPUpdateRequest.Name = source.Name + if source.Labels != nil { + schemaPrimaryIPUpdateRequest.Labels = (*source.Labels) + } + schemaPrimaryIPUpdateRequest.AutoDelete = source.AutoDelete + return schemaPrimaryIPUpdateRequest +} func (c *converterImpl) SchemaFromSSHKey(source *SSHKey) schema.SSHKey { var schemaSSHKey schema.SSHKey if source != nil { @@ -1082,14 +1039,12 @@ func (c *converterImpl) SchemaFromServer(source *Server) schema.Server { schemaServer2.Status = string((*source).Status) schemaServer2.Created = c.timeTimeToTimeTime((*source).Created) schemaServer2.PublicNet = c.SchemaFromServerPublicNet((*source).PublicNet) - var schemaServerPrivateNetList []schema.ServerPrivateNet if (*source).PrivateNet != nil { - schemaServerPrivateNetList = make([]schema.ServerPrivateNet, len((*source).PrivateNet)) + schemaServer2.PrivateNet = make([]schema.ServerPrivateNet, len((*source).PrivateNet)) for i := 0; i < len((*source).PrivateNet); i++ { - schemaServerPrivateNetList[i] = c.SchemaFromServerPrivateNet((*source).PrivateNet[i]) + schemaServer2.PrivateNet[i] = c.SchemaFromServerPrivateNet((*source).PrivateNet[i]) } } - schemaServer2.PrivateNet = schemaServerPrivateNetList schemaServer2.ServerType = c.SchemaFromServerType((*source).ServerType) schemaServer2.IncludedTraffic = (*source).IncludedTraffic schemaServer2.OutgoingTraffic = mapZeroUint64ToNil((*source).OutgoingTraffic) @@ -1102,24 +1057,20 @@ func (c *converterImpl) SchemaFromServer(source *Server) schema.Server { schemaServer2.Image = c.pHcloudImageToPSchemaImage((*source).Image) schemaServer2.Protection = c.hcloudServerProtectionToSchemaServerProtection((*source).Protection) schemaServer2.Labels = (*source).Labels - var int64List []int64 if (*source).Volumes != nil { - int64List = make([]int64, len((*source).Volumes)) + schemaServer2.Volumes = make([]int64, len((*source).Volumes)) for j := 0; j < len((*source).Volumes); j++ { - int64List[j] = int64FromVolume((*source).Volumes[j]) + schemaServer2.Volumes[j] = int64FromVolume((*source).Volumes[j]) } } - schemaServer2.Volumes = int64List schemaServer2.PrimaryDiskSize = (*source).PrimaryDiskSize schemaServer2.PlacementGroup = c.pHcloudPlacementGroupToPSchemaPlacementGroup((*source).PlacementGroup) - var int64List2 []int64 if (*source).LoadBalancers != nil { - int64List2 = make([]int64, len((*source).LoadBalancers)) + schemaServer2.LoadBalancers = make([]int64, len((*source).LoadBalancers)) for k := 0; k < len((*source).LoadBalancers); k++ { - int64List2[k] = c.pHcloudLoadBalancerToInt64((*source).LoadBalancers[k]) + schemaServer2.LoadBalancers[k] = c.pHcloudLoadBalancerToInt64((*source).LoadBalancers[k]) } } - schemaServer2.LoadBalancers = int64List2 schemaServer = schemaServer2 } return schemaServer @@ -1128,14 +1079,12 @@ func (c *converterImpl) SchemaFromServerPrivateNet(source ServerPrivateNet) sche var schemaServerPrivateNet schema.ServerPrivateNet schemaServerPrivateNet.Network = c.pHcloudNetworkToInt64(source.Network) schemaServerPrivateNet.IP = stringFromIP(source.IP) - var stringList []string if source.Aliases != nil { - stringList = make([]string, len(source.Aliases)) + schemaServerPrivateNet.AliasIPs = make([]string, len(source.Aliases)) for i := 0; i < len(source.Aliases); i++ { - stringList[i] = stringFromIP(source.Aliases[i]) + schemaServerPrivateNet.AliasIPs[i] = stringFromIP(source.Aliases[i]) } } - schemaServerPrivateNet.AliasIPs = stringList schemaServerPrivateNet.MACAddress = source.MACAddress return schemaServerPrivateNet } @@ -1143,22 +1092,18 @@ func (c *converterImpl) SchemaFromServerPublicNet(source ServerPublicNet) schema var schemaServerPublicNet schema.ServerPublicNet schemaServerPublicNet.IPv4 = c.SchemaFromServerPublicNetIPv4(source.IPv4) schemaServerPublicNet.IPv6 = c.SchemaFromServerPublicNetIPv6(source.IPv6) - var int64List []int64 if source.FloatingIPs != nil { - int64List = make([]int64, len(source.FloatingIPs)) + schemaServerPublicNet.FloatingIPs = make([]int64, len(source.FloatingIPs)) for i := 0; i < len(source.FloatingIPs); i++ { - int64List[i] = int64FromFloatingIP(source.FloatingIPs[i]) + schemaServerPublicNet.FloatingIPs[i] = int64FromFloatingIP(source.FloatingIPs[i]) } } - schemaServerPublicNet.FloatingIPs = int64List - var schemaServerFirewallList []schema.ServerFirewall if source.Firewalls != nil { - schemaServerFirewallList = make([]schema.ServerFirewall, len(source.Firewalls)) + schemaServerPublicNet.Firewalls = make([]schema.ServerFirewall, len(source.Firewalls)) for j := 0; j < len(source.Firewalls); j++ { - schemaServerFirewallList[j] = serverFirewallSchemaFromFirewallStatus(source.Firewalls[j]) + schemaServerPublicNet.Firewalls[j] = serverFirewallSchemaFromFirewallStatus(source.Firewalls[j]) } } - schemaServerPublicNet.Firewalls = schemaServerFirewallList return schemaServerPublicNet } func (c *converterImpl) SchemaFromServerPublicNetIPv4(source ServerPublicNetIPv4) schema.ServerPublicNetIPv4 { @@ -1191,14 +1136,12 @@ func (c *converterImpl) SchemaFromServerType(source *ServerType) schema.ServerTy schemaServerType2.CPUType = string((*source).CPUType) schemaServerType2.Architecture = string((*source).Architecture) schemaServerType2.IncludedTraffic = (*source).IncludedTraffic - var schemaPricingServerTypePriceList []schema.PricingServerTypePrice if (*source).Pricings != nil { - schemaPricingServerTypePriceList = make([]schema.PricingServerTypePrice, len((*source).Pricings)) + schemaServerType2.Prices = make([]schema.PricingServerTypePrice, len((*source).Pricings)) for i := 0; i < len((*source).Pricings); i++ { - schemaPricingServerTypePriceList[i] = c.schemaFromServerTypeLocationPricing((*source).Pricings[i]) + schemaServerType2.Prices[i] = c.schemaFromServerTypeLocationPricing((*source).Pricings[i]) } } - schemaServerType2.Prices = schemaPricingServerTypePriceList schemaServerType2.Deprecated = isDeprecationNotNil((*source).DeprecatableResource.Deprecation) schemaServerType2.DeprecatableResource = c.hcloudDeprecatableResourceToSchemaDeprecatableResource((*source).DeprecatableResource) schemaServerType = schemaServerType2 @@ -1231,57 +1174,45 @@ func (c *converterImpl) ServerFromSchema(source schema.Server) *Server { hcloudServer.Status = ServerStatus(source.Status) hcloudServer.Created = c.timeTimeToTimeTime(source.Created) hcloudServer.PublicNet = c.ServerPublicNetFromSchema(source.PublicNet) - var hcloudServerPrivateNetList []ServerPrivateNet if source.PrivateNet != nil { - hcloudServerPrivateNetList = make([]ServerPrivateNet, len(source.PrivateNet)) + hcloudServer.PrivateNet = make([]ServerPrivateNet, len(source.PrivateNet)) for i := 0; i < len(source.PrivateNet); i++ { - hcloudServerPrivateNetList[i] = c.ServerPrivateNetFromSchema(source.PrivateNet[i]) + hcloudServer.PrivateNet[i] = c.ServerPrivateNetFromSchema(source.PrivateNet[i]) } } - hcloudServer.PrivateNet = hcloudServerPrivateNetList hcloudServer.ServerType = c.ServerTypeFromSchema(source.ServerType) hcloudServer.Datacenter = c.DatacenterFromSchema(source.Datacenter) hcloudServer.IncludedTraffic = source.IncludedTraffic - var xuint64 uint64 if source.OutgoingTraffic != nil { - xuint64 = *source.OutgoingTraffic + hcloudServer.OutgoingTraffic = *source.OutgoingTraffic } - hcloudServer.OutgoingTraffic = xuint64 - var xuint642 uint64 if source.IngoingTraffic != nil { - xuint642 = *source.IngoingTraffic + hcloudServer.IngoingTraffic = *source.IngoingTraffic } - hcloudServer.IngoingTraffic = xuint642 - var xstring string if source.BackupWindow != nil { - xstring = *source.BackupWindow + hcloudServer.BackupWindow = *source.BackupWindow } - hcloudServer.BackupWindow = xstring hcloudServer.RescueEnabled = source.RescueEnabled hcloudServer.Locked = source.Locked hcloudServer.ISO = c.pSchemaISOToPHcloudISO(source.ISO) hcloudServer.Image = c.pSchemaImageToPHcloudImage(source.Image) hcloudServer.Protection = c.schemaServerProtectionToHcloudServerProtection(source.Protection) hcloudServer.Labels = source.Labels - var pHcloudVolumeList []*Volume if source.Volumes != nil { - pHcloudVolumeList = make([]*Volume, len(source.Volumes)) + hcloudServer.Volumes = make([]*Volume, len(source.Volumes)) for j := 0; j < len(source.Volumes); j++ { - pHcloudVolumeList[j] = volumeFromInt64(source.Volumes[j]) + hcloudServer.Volumes[j] = volumeFromInt64(source.Volumes[j]) } } - hcloudServer.Volumes = pHcloudVolumeList hcloudServer.PrimaryDiskSize = source.PrimaryDiskSize hcloudServer.PlacementGroup = c.pSchemaPlacementGroupToPHcloudPlacementGroup(source.PlacementGroup) - var pHcloudLoadBalancerList []*LoadBalancer if source.LoadBalancers != nil { - pHcloudLoadBalancerList = make([]*LoadBalancer, len(source.LoadBalancers)) + hcloudServer.LoadBalancers = make([]*LoadBalancer, len(source.LoadBalancers)) for k := 0; k < len(source.LoadBalancers); k++ { hcloudLoadBalancer := loadBalancerFromInt64(source.LoadBalancers[k]) - pHcloudLoadBalancerList[k] = &hcloudLoadBalancer + hcloudServer.LoadBalancers[k] = &hcloudLoadBalancer } } - hcloudServer.LoadBalancers = pHcloudLoadBalancerList return &hcloudServer } func (c *converterImpl) ServerMetricsFromSchema(source *schema.ServerGetMetricsResponse) (*ServerMetrics, error) { @@ -1291,18 +1222,16 @@ func (c *converterImpl) ServerMetricsFromSchema(source *schema.ServerGetMetricsR hcloudServerMetrics.Start = c.timeTimeToTimeTime((*source).Metrics.Start) hcloudServerMetrics.End = c.timeTimeToTimeTime((*source).Metrics.End) hcloudServerMetrics.Step = (*source).Metrics.Step - var mapStringHcloudServerMetricsValueList map[string][]ServerMetricsValue if (*source).Metrics.TimeSeries != nil { - mapStringHcloudServerMetricsValueList = make(map[string][]ServerMetricsValue, len((*source).Metrics.TimeSeries)) + hcloudServerMetrics.TimeSeries = make(map[string][]ServerMetricsValue, len((*source).Metrics.TimeSeries)) for key, value := range (*source).Metrics.TimeSeries { hcloudServerMetricsValueList, err := serverMetricsTimeSeriesFromSchema(value) if err != nil { return nil, err } - mapStringHcloudServerMetricsValueList[key] = hcloudServerMetricsValueList + hcloudServerMetrics.TimeSeries[key] = hcloudServerMetricsValueList } } - hcloudServerMetrics.TimeSeries = mapStringHcloudServerMetricsValueList pHcloudServerMetrics = &hcloudServerMetrics } return pHcloudServerMetrics, nil @@ -1312,14 +1241,12 @@ func (c *converterImpl) ServerPrivateNetFromSchema(source schema.ServerPrivateNe hcloudNetwork := networkFromInt64(source.Network) hcloudServerPrivateNet.Network = &hcloudNetwork hcloudServerPrivateNet.IP = ipFromString(source.IP) - var netIPList []net.IP if source.AliasIPs != nil { - netIPList = make([]net.IP, len(source.AliasIPs)) + hcloudServerPrivateNet.Aliases = make([]net.IP, len(source.AliasIPs)) for i := 0; i < len(source.AliasIPs); i++ { - netIPList[i] = ipFromString(source.AliasIPs[i]) + hcloudServerPrivateNet.Aliases[i] = ipFromString(source.AliasIPs[i]) } } - hcloudServerPrivateNet.Aliases = netIPList hcloudServerPrivateNet.MACAddress = source.MACAddress return hcloudServerPrivateNet } @@ -1327,22 +1254,18 @@ func (c *converterImpl) ServerPublicNetFromSchema(source schema.ServerPublicNet) var hcloudServerPublicNet ServerPublicNet hcloudServerPublicNet.IPv4 = c.ServerPublicNetIPv4FromSchema(source.IPv4) hcloudServerPublicNet.IPv6 = c.ServerPublicNetIPv6FromSchema(source.IPv6) - var pHcloudFloatingIPList []*FloatingIP if source.FloatingIPs != nil { - pHcloudFloatingIPList = make([]*FloatingIP, len(source.FloatingIPs)) + hcloudServerPublicNet.FloatingIPs = make([]*FloatingIP, len(source.FloatingIPs)) for i := 0; i < len(source.FloatingIPs); i++ { - pHcloudFloatingIPList[i] = floatingIPFromInt64(source.FloatingIPs[i]) + hcloudServerPublicNet.FloatingIPs[i] = floatingIPFromInt64(source.FloatingIPs[i]) } } - hcloudServerPublicNet.FloatingIPs = pHcloudFloatingIPList - var pHcloudServerFirewallStatusList []*ServerFirewallStatus if source.Firewalls != nil { - pHcloudServerFirewallStatusList = make([]*ServerFirewallStatus, len(source.Firewalls)) + hcloudServerPublicNet.Firewalls = make([]*ServerFirewallStatus, len(source.Firewalls)) for j := 0; j < len(source.Firewalls); j++ { - pHcloudServerFirewallStatusList[j] = firewallStatusFromSchemaServerFirewall(source.Firewalls[j]) + hcloudServerPublicNet.Firewalls[j] = firewallStatusFromSchemaServerFirewall(source.Firewalls[j]) } } - hcloudServerPublicNet.Firewalls = pHcloudServerFirewallStatusList return hcloudServerPublicNet } func (c *converterImpl) ServerPublicNetIPv4FromSchema(source schema.ServerPublicNetIPv4) ServerPublicNetIPv4 { @@ -1374,14 +1297,12 @@ func (c *converterImpl) ServerTypeFromSchema(source schema.ServerType) *ServerTy hcloudServerType.CPUType = CPUType(source.CPUType) hcloudServerType.Architecture = Architecture(source.Architecture) hcloudServerType.IncludedTraffic = source.IncludedTraffic - var hcloudServerTypeLocationPricingList []ServerTypeLocationPricing if source.Prices != nil { - hcloudServerTypeLocationPricingList = make([]ServerTypeLocationPricing, len(source.Prices)) + hcloudServerType.Pricings = make([]ServerTypeLocationPricing, len(source.Prices)) for i := 0; i < len(source.Prices); i++ { - hcloudServerTypeLocationPricingList[i] = c.serverTypePricingFromSchema(source.Prices[i]) + hcloudServerType.Pricings[i] = c.serverTypePricingFromSchema(source.Prices[i]) } } - hcloudServerType.Pricings = hcloudServerTypeLocationPricingList hcloudServerType.DeprecatableResource = c.schemaDeprecatableResourceToHcloudDeprecatableResource(source.DeprecatableResource) return &hcloudServerType } @@ -1390,12 +1311,10 @@ func (c *converterImpl) VolumeFromSchema(source schema.Volume) *Volume { hcloudVolume.ID = source.ID hcloudVolume.Name = source.Name hcloudVolume.Status = VolumeStatus(source.Status) - var pHcloudServer *Server if source.Server != nil { hcloudServer := serverFromInt64(*source.Server) - pHcloudServer = &hcloudServer + hcloudVolume.Server = &hcloudServer } - hcloudVolume.Server = pHcloudServer hcloudVolume.Location = c.LocationFromSchema(source.Location) hcloudVolume.Size = source.Size hcloudVolume.Format = source.Format @@ -1416,30 +1335,24 @@ func (c *converterImpl) hcloudCertificateUsedByRefToSchemaCertificateUsedByRef(s } func (c *converterImpl) hcloudDatacenterServerTypesToSchemaDatacenterServerTypes(source DatacenterServerTypes) schema.DatacenterServerTypes { var schemaDatacenterServerTypes schema.DatacenterServerTypes - var int64List []int64 if source.Supported != nil { - int64List = make([]int64, len(source.Supported)) + schemaDatacenterServerTypes.Supported = make([]int64, len(source.Supported)) for i := 0; i < len(source.Supported); i++ { - int64List[i] = int64FromServerType(source.Supported[i]) + schemaDatacenterServerTypes.Supported[i] = int64FromServerType(source.Supported[i]) } } - schemaDatacenterServerTypes.Supported = int64List - var int64List2 []int64 if source.AvailableForMigration != nil { - int64List2 = make([]int64, len(source.AvailableForMigration)) + schemaDatacenterServerTypes.AvailableForMigration = make([]int64, len(source.AvailableForMigration)) for j := 0; j < len(source.AvailableForMigration); j++ { - int64List2[j] = int64FromServerType(source.AvailableForMigration[j]) + schemaDatacenterServerTypes.AvailableForMigration[j] = int64FromServerType(source.AvailableForMigration[j]) } } - schemaDatacenterServerTypes.AvailableForMigration = int64List2 - var int64List3 []int64 if source.Available != nil { - int64List3 = make([]int64, len(source.Available)) + schemaDatacenterServerTypes.Available = make([]int64, len(source.Available)) for k := 0; k < len(source.Available); k++ { - int64List3[k] = int64FromServerType(source.Available[k]) + schemaDatacenterServerTypes.Available[k] = int64FromServerType(source.Available[k]) } } - schemaDatacenterServerTypes.Available = int64List3 return schemaDatacenterServerTypes } func (c *converterImpl) hcloudDeprecatableResourceToSchemaDeprecatableResource(source DeprecatableResource) schema.DeprecatableResource { @@ -1450,22 +1363,18 @@ func (c *converterImpl) hcloudDeprecatableResourceToSchemaDeprecatableResource(s func (c *converterImpl) hcloudFirewallRuleToSchemaFirewallRule(source FirewallRule) schema.FirewallRule { var schemaFirewallRule schema.FirewallRule schemaFirewallRule.Direction = string(source.Direction) - var stringList []string if source.SourceIPs != nil { - stringList = make([]string, len(source.SourceIPs)) + schemaFirewallRule.SourceIPs = make([]string, len(source.SourceIPs)) for i := 0; i < len(source.SourceIPs); i++ { - stringList[i] = stringFromIPNet(source.SourceIPs[i]) + schemaFirewallRule.SourceIPs[i] = stringFromIPNet(source.SourceIPs[i]) } } - schemaFirewallRule.SourceIPs = stringList - var stringList2 []string if source.DestinationIPs != nil { - stringList2 = make([]string, len(source.DestinationIPs)) + schemaFirewallRule.DestinationIPs = make([]string, len(source.DestinationIPs)) for j := 0; j < len(source.DestinationIPs); j++ { - stringList2[j] = stringFromIPNet(source.DestinationIPs[j]) + schemaFirewallRule.DestinationIPs[j] = stringFromIPNet(source.DestinationIPs[j]) } } - schemaFirewallRule.DestinationIPs = stringList2 schemaFirewallRule.Protocol = string(source.Protocol) schemaFirewallRule.Port = source.Port schemaFirewallRule.Description = source.Description @@ -1474,22 +1383,18 @@ func (c *converterImpl) hcloudFirewallRuleToSchemaFirewallRule(source FirewallRu func (c *converterImpl) hcloudFirewallRuleToSchemaFirewallRuleRequest(source FirewallRule) schema.FirewallRuleRequest { var schemaFirewallRuleRequest schema.FirewallRuleRequest schemaFirewallRuleRequest.Direction = string(source.Direction) - var stringList []string if source.SourceIPs != nil { - stringList = make([]string, len(source.SourceIPs)) + schemaFirewallRuleRequest.SourceIPs = make([]string, len(source.SourceIPs)) for i := 0; i < len(source.SourceIPs); i++ { - stringList[i] = stringFromIPNet(source.SourceIPs[i]) + schemaFirewallRuleRequest.SourceIPs[i] = stringFromIPNet(source.SourceIPs[i]) } } - schemaFirewallRuleRequest.SourceIPs = stringList - var stringList2 []string if source.DestinationIPs != nil { - stringList2 = make([]string, len(source.DestinationIPs)) + schemaFirewallRuleRequest.DestinationIPs = make([]string, len(source.DestinationIPs)) for j := 0; j < len(source.DestinationIPs); j++ { - stringList2[j] = stringFromIPNet(source.DestinationIPs[j]) + schemaFirewallRuleRequest.DestinationIPs[j] = stringFromIPNet(source.DestinationIPs[j]) } } - schemaFirewallRuleRequest.DestinationIPs = stringList2 schemaFirewallRuleRequest.Protocol = string(source.Protocol) schemaFirewallRuleRequest.Port = source.Port schemaFirewallRuleRequest.Description = source.Description @@ -1563,14 +1468,12 @@ func (c *converterImpl) hcloudLoadBalancerServiceHTTPToPSchemaLoadBalancerServic var schemaLoadBalancerServiceHTTP schema.LoadBalancerServiceHTTP schemaLoadBalancerServiceHTTP.CookieName = source.CookieName schemaLoadBalancerServiceHTTP.CookieLifetime = intSecondsFromDuration(source.CookieLifetime) - var int64List []int64 if source.Certificates != nil { - int64List = make([]int64, len(source.Certificates)) + schemaLoadBalancerServiceHTTP.Certificates = make([]int64, len(source.Certificates)) for i := 0; i < len(source.Certificates); i++ { - int64List[i] = int64FromCertificate(source.Certificates[i]) + schemaLoadBalancerServiceHTTP.Certificates[i] = int64FromCertificate(source.Certificates[i]) } } - schemaLoadBalancerServiceHTTP.Certificates = int64List schemaLoadBalancerServiceHTTP.RedirectHTTP = source.RedirectHTTP schemaLoadBalancerServiceHTTP.StickySessions = source.StickySessions return &schemaLoadBalancerServiceHTTP @@ -1619,12 +1522,10 @@ func (c *converterImpl) intISOFromSchema(source schema.ISO) ISO { hcloudISO.Name = source.Name hcloudISO.Description = source.Description hcloudISO.Type = ISOType(source.Type) - var pHcloudArchitecture *Architecture if source.Architecture != nil { hcloudArchitecture := Architecture(*source.Architecture) - pHcloudArchitecture = &hcloudArchitecture + hcloudISO.Architecture = &hcloudArchitecture } - hcloudISO.Architecture = pHcloudArchitecture var pTimeTime *time.Time if source.DeprecatableResource.Deprecation != nil { pTimeTime = &source.DeprecatableResource.Deprecation.UnavailableAfter @@ -1712,12 +1613,10 @@ func (c *converterImpl) pHcloudISOToPSchemaISO(source *ISO) *schema.ISO { schemaISO.Name = (*source).Name schemaISO.Description = (*source).Description schemaISO.Type = string((*source).Type) - var pString *string if (*source).Architecture != nil { xstring := string(*(*source).Architecture) - pString = &xstring + schemaISO.Architecture = &xstring } - schemaISO.Architecture = pString schemaISO.DeprecatableResource = c.hcloudDeprecatableResourceToSchemaDeprecatableResource((*source).DeprecatableResource) pSchemaISO = &schemaISO } @@ -1736,12 +1635,10 @@ func (c *converterImpl) pHcloudLoadBalancerAddServiceOptsHTTPToPSchemaLoadBalanc if source != nil { var schemaLoadBalancerActionAddServiceRequestHTTP schema.LoadBalancerActionAddServiceRequestHTTP schemaLoadBalancerActionAddServiceRequestHTTP.CookieName = (*source).CookieName - var pInt *int if (*source).CookieLifetime != nil { xint := intSecondsFromDuration(*(*source).CookieLifetime) - pInt = &xint + schemaLoadBalancerActionAddServiceRequestHTTP.CookieLifetime = &xint } - schemaLoadBalancerActionAddServiceRequestHTTP.CookieLifetime = pInt schemaLoadBalancerActionAddServiceRequestHTTP.Certificates = int64SlicePtrFromCertificatePtrSlice((*source).Certificates) schemaLoadBalancerActionAddServiceRequestHTTP.RedirectHTTP = (*source).RedirectHTTP schemaLoadBalancerActionAddServiceRequestHTTP.StickySessions = (*source).StickySessions @@ -1768,18 +1665,14 @@ func (c *converterImpl) pHcloudLoadBalancerAddServiceOptsHealthCheckToPSchemaLoa var schemaLoadBalancerActionAddServiceRequestHealthCheck schema.LoadBalancerActionAddServiceRequestHealthCheck schemaLoadBalancerActionAddServiceRequestHealthCheck.Protocol = string((*source).Protocol) schemaLoadBalancerActionAddServiceRequestHealthCheck.Port = (*source).Port - var pInt *int if (*source).Interval != nil { xint := intSecondsFromDuration(*(*source).Interval) - pInt = &xint + schemaLoadBalancerActionAddServiceRequestHealthCheck.Interval = &xint } - schemaLoadBalancerActionAddServiceRequestHealthCheck.Interval = pInt - var pInt2 *int if (*source).Timeout != nil { xint2 := intSecondsFromDuration(*(*source).Timeout) - pInt2 = &xint2 + schemaLoadBalancerActionAddServiceRequestHealthCheck.Timeout = &xint2 } - schemaLoadBalancerActionAddServiceRequestHealthCheck.Timeout = pInt2 schemaLoadBalancerActionAddServiceRequestHealthCheck.Retries = (*source).Retries schemaLoadBalancerActionAddServiceRequestHealthCheck.HTTP = c.pHcloudLoadBalancerAddServiceOptsHealthCheckHTTPToPSchemaLoadBalancerActionAddServiceRequestHealthCheckHTTP((*source).HTTP) pSchemaLoadBalancerActionAddServiceRequestHealthCheck = &schemaLoadBalancerActionAddServiceRequestHealthCheck @@ -1800,12 +1693,10 @@ func (c *converterImpl) pHcloudLoadBalancerCreateOptsServiceHTTPToPSchemaLoadBal if source != nil { var schemaLoadBalancerCreateRequestServiceHTTP schema.LoadBalancerCreateRequestServiceHTTP schemaLoadBalancerCreateRequestServiceHTTP.CookieName = (*source).CookieName - var pInt *int if (*source).CookieLifetime != nil { xint := intSecondsFromDuration(*(*source).CookieLifetime) - pInt = &xint + schemaLoadBalancerCreateRequestServiceHTTP.CookieLifetime = &xint } - schemaLoadBalancerCreateRequestServiceHTTP.CookieLifetime = pInt schemaLoadBalancerCreateRequestServiceHTTP.Certificates = int64SlicePtrFromCertificatePtrSlice((*source).Certificates) schemaLoadBalancerCreateRequestServiceHTTP.RedirectHTTP = (*source).RedirectHTTP schemaLoadBalancerCreateRequestServiceHTTP.StickySessions = (*source).StickySessions @@ -1832,18 +1723,14 @@ func (c *converterImpl) pHcloudLoadBalancerCreateOptsServiceHealthCheckToPSchema var schemaLoadBalancerCreateRequestServiceHealthCheck schema.LoadBalancerCreateRequestServiceHealthCheck schemaLoadBalancerCreateRequestServiceHealthCheck.Protocol = string((*source).Protocol) schemaLoadBalancerCreateRequestServiceHealthCheck.Port = (*source).Port - var pInt *int if (*source).Interval != nil { xint := intSecondsFromDuration(*(*source).Interval) - pInt = &xint + schemaLoadBalancerCreateRequestServiceHealthCheck.Interval = &xint } - schemaLoadBalancerCreateRequestServiceHealthCheck.Interval = pInt - var pInt2 *int if (*source).Timeout != nil { xint2 := intSecondsFromDuration(*(*source).Timeout) - pInt2 = &xint2 + schemaLoadBalancerCreateRequestServiceHealthCheck.Timeout = &xint2 } - schemaLoadBalancerCreateRequestServiceHealthCheck.Timeout = pInt2 schemaLoadBalancerCreateRequestServiceHealthCheck.Retries = (*source).Retries schemaLoadBalancerCreateRequestServiceHealthCheck.HTTP = c.pHcloudLoadBalancerCreateOptsServiceHealthCheckHTTPToPSchemaLoadBalancerCreateRequestServiceHealthCheckHTTP((*source).HTTP) pSchemaLoadBalancerCreateRequestServiceHealthCheck = &schemaLoadBalancerCreateRequestServiceHealthCheck @@ -1896,17 +1783,25 @@ func (c *converterImpl) pHcloudLoadBalancerToInt64(source *LoadBalancer) int64 { } return xint64 } +func (c *converterImpl) pHcloudLoadBalancerTypeToSchemaIDOrName(source *LoadBalancerType) schema.IDOrName { + var schemaIDOrName schema.IDOrName + if source != nil { + var schemaIDOrName2 schema.IDOrName + schemaIDOrName2.ID = (*source).ID + schemaIDOrName2.Name = (*source).Name + schemaIDOrName = schemaIDOrName2 + } + return schemaIDOrName +} func (c *converterImpl) pHcloudLoadBalancerUpdateServiceOptsHTTPToPSchemaLoadBalancerActionUpdateServiceRequestHTTP(source *LoadBalancerUpdateServiceOptsHTTP) *schema.LoadBalancerActionUpdateServiceRequestHTTP { var pSchemaLoadBalancerActionUpdateServiceRequestHTTP *schema.LoadBalancerActionUpdateServiceRequestHTTP if source != nil { var schemaLoadBalancerActionUpdateServiceRequestHTTP schema.LoadBalancerActionUpdateServiceRequestHTTP schemaLoadBalancerActionUpdateServiceRequestHTTP.CookieName = (*source).CookieName - var pInt *int if (*source).CookieLifetime != nil { xint := intSecondsFromDuration(*(*source).CookieLifetime) - pInt = &xint + schemaLoadBalancerActionUpdateServiceRequestHTTP.CookieLifetime = &xint } - schemaLoadBalancerActionUpdateServiceRequestHTTP.CookieLifetime = pInt schemaLoadBalancerActionUpdateServiceRequestHTTP.Certificates = int64SlicePtrFromCertificatePtrSlice((*source).Certificates) schemaLoadBalancerActionUpdateServiceRequestHTTP.RedirectHTTP = (*source).RedirectHTTP schemaLoadBalancerActionUpdateServiceRequestHTTP.StickySessions = (*source).StickySessions @@ -1933,18 +1828,14 @@ func (c *converterImpl) pHcloudLoadBalancerUpdateServiceOptsHealthCheckToPSchema var schemaLoadBalancerActionUpdateServiceRequestHealthCheck schema.LoadBalancerActionUpdateServiceRequestHealthCheck schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Protocol = stringPtrFromLoadBalancerServiceProtocol((*source).Protocol) schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Port = (*source).Port - var pInt *int if (*source).Interval != nil { xint := intSecondsFromDuration(*(*source).Interval) - pInt = &xint + schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Interval = &xint } - schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Interval = pInt - var pInt2 *int if (*source).Timeout != nil { xint2 := intSecondsFromDuration(*(*source).Timeout) - pInt2 = &xint2 + schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Timeout = &xint2 } - schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Timeout = pInt2 schemaLoadBalancerActionUpdateServiceRequestHealthCheck.Retries = (*source).Retries schemaLoadBalancerActionUpdateServiceRequestHealthCheck.HTTP = c.pHcloudLoadBalancerUpdateServiceOptsHealthCheckHTTPToPSchemaLoadBalancerActionUpdateServiceRequestHealthCheckHTTP((*source).HTTP) pSchemaLoadBalancerActionUpdateServiceRequestHealthCheck = &schemaLoadBalancerActionUpdateServiceRequestHealthCheck @@ -2085,35 +1976,27 @@ func (c *converterImpl) pSchemaImageToPHcloudImage(source *schema.Image) *Image if source != nil { var hcloudImage Image hcloudImage.ID = (*source).ID - var xstring string if (*source).Name != nil { - xstring = *(*source).Name + hcloudImage.Name = *(*source).Name } - hcloudImage.Name = xstring hcloudImage.Type = ImageType((*source).Type) hcloudImage.Status = ImageStatus((*source).Status) hcloudImage.Description = (*source).Description - var xfloat32 float32 if (*source).ImageSize != nil { - xfloat32 = *(*source).ImageSize + hcloudImage.ImageSize = *(*source).ImageSize } - hcloudImage.ImageSize = xfloat32 hcloudImage.DiskSize = (*source).DiskSize hcloudImage.Created = c.pTimeTimeToTimeTime((*source).Created) hcloudImage.CreatedFrom = c.pSchemaImageCreatedFromToPHcloudServer((*source).CreatedFrom) - var pHcloudServer *Server if (*source).BoundTo != nil { hcloudServer := serverFromInt64(*(*source).BoundTo) - pHcloudServer = &hcloudServer + hcloudImage.BoundTo = &hcloudServer } - hcloudImage.BoundTo = pHcloudServer hcloudImage.RapidDeploy = (*source).RapidDeploy hcloudImage.OSFlavor = (*source).OSFlavor - var xstring2 string if (*source).OSVersion != nil { - xstring2 = *(*source).OSVersion + hcloudImage.OSVersion = *(*source).OSVersion } - hcloudImage.OSVersion = xstring2 hcloudImage.Architecture = Architecture((*source).Architecture) hcloudImage.Protection = c.schemaImageProtectionToHcloudImageProtection((*source).Protection) hcloudImage.Deprecated = c.pTimeTimeToTimeTime((*source).Deprecated) @@ -2129,14 +2012,12 @@ func (c *converterImpl) pSchemaLoadBalancerServiceHTTPToHcloudLoadBalancerServic var hcloudLoadBalancerServiceHTTP2 LoadBalancerServiceHTTP hcloudLoadBalancerServiceHTTP2.CookieName = (*source).CookieName hcloudLoadBalancerServiceHTTP2.CookieLifetime = durationFromIntSeconds((*source).CookieLifetime) - var pHcloudCertificateList []*Certificate if (*source).Certificates != nil { - pHcloudCertificateList = make([]*Certificate, len((*source).Certificates)) + hcloudLoadBalancerServiceHTTP2.Certificates = make([]*Certificate, len((*source).Certificates)) for i := 0; i < len((*source).Certificates); i++ { - pHcloudCertificateList[i] = certificateFromInt64((*source).Certificates[i]) + hcloudLoadBalancerServiceHTTP2.Certificates[i] = certificateFromInt64((*source).Certificates[i]) } } - hcloudLoadBalancerServiceHTTP2.Certificates = pHcloudCertificateList hcloudLoadBalancerServiceHTTP2.RedirectHTTP = (*source).RedirectHTTP hcloudLoadBalancerServiceHTTP2.StickySessions = (*source).StickySessions hcloudLoadBalancerServiceHTTP = hcloudLoadBalancerServiceHTTP2 @@ -2217,30 +2098,24 @@ func (c *converterImpl) schemaCertificateUsedByRefToHcloudCertificateUsedByRef(s } func (c *converterImpl) schemaDatacenterServerTypesToHcloudDatacenterServerTypes(source schema.DatacenterServerTypes) DatacenterServerTypes { var hcloudDatacenterServerTypes DatacenterServerTypes - var pHcloudServerTypeList []*ServerType if source.Supported != nil { - pHcloudServerTypeList = make([]*ServerType, len(source.Supported)) + hcloudDatacenterServerTypes.Supported = make([]*ServerType, len(source.Supported)) for i := 0; i < len(source.Supported); i++ { - pHcloudServerTypeList[i] = serverTypeFromInt64(source.Supported[i]) + hcloudDatacenterServerTypes.Supported[i] = serverTypeFromInt64(source.Supported[i]) } } - hcloudDatacenterServerTypes.Supported = pHcloudServerTypeList - var pHcloudServerTypeList2 []*ServerType if source.AvailableForMigration != nil { - pHcloudServerTypeList2 = make([]*ServerType, len(source.AvailableForMigration)) + hcloudDatacenterServerTypes.AvailableForMigration = make([]*ServerType, len(source.AvailableForMigration)) for j := 0; j < len(source.AvailableForMigration); j++ { - pHcloudServerTypeList2[j] = serverTypeFromInt64(source.AvailableForMigration[j]) + hcloudDatacenterServerTypes.AvailableForMigration[j] = serverTypeFromInt64(source.AvailableForMigration[j]) } } - hcloudDatacenterServerTypes.AvailableForMigration = pHcloudServerTypeList2 - var pHcloudServerTypeList3 []*ServerType if source.Available != nil { - pHcloudServerTypeList3 = make([]*ServerType, len(source.Available)) + hcloudDatacenterServerTypes.Available = make([]*ServerType, len(source.Available)) for k := 0; k < len(source.Available); k++ { - pHcloudServerTypeList3[k] = serverTypeFromInt64(source.Available[k]) + hcloudDatacenterServerTypes.Available[k] = serverTypeFromInt64(source.Available[k]) } } - hcloudDatacenterServerTypes.Available = pHcloudServerTypeList3 return hcloudDatacenterServerTypes } func (c *converterImpl) schemaDeprecatableResourceToHcloudDeprecatableResource(source schema.DeprecatableResource) DeprecatableResource { @@ -2258,22 +2133,18 @@ func (c *converterImpl) schemaFirewallResourceToHcloudFirewallResource(source sc func (c *converterImpl) schemaFirewallRuleToHcloudFirewallRule(source schema.FirewallRule) FirewallRule { var hcloudFirewallRule FirewallRule hcloudFirewallRule.Direction = FirewallRuleDirection(source.Direction) - var netIPNetList []net.IPNet if source.SourceIPs != nil { - netIPNetList = make([]net.IPNet, len(source.SourceIPs)) + hcloudFirewallRule.SourceIPs = make([]net.IPNet, len(source.SourceIPs)) for i := 0; i < len(source.SourceIPs); i++ { - netIPNetList[i] = ipNetFromString(source.SourceIPs[i]) + hcloudFirewallRule.SourceIPs[i] = ipNetFromString(source.SourceIPs[i]) } } - hcloudFirewallRule.SourceIPs = netIPNetList - var netIPNetList2 []net.IPNet if source.DestinationIPs != nil { - netIPNetList2 = make([]net.IPNet, len(source.DestinationIPs)) + hcloudFirewallRule.DestinationIPs = make([]net.IPNet, len(source.DestinationIPs)) for j := 0; j < len(source.DestinationIPs); j++ { - netIPNetList2[j] = ipNetFromString(source.DestinationIPs[j]) + hcloudFirewallRule.DestinationIPs[j] = ipNetFromString(source.DestinationIPs[j]) } } - hcloudFirewallRule.DestinationIPs = netIPNetList2 hcloudFirewallRule.Protocol = FirewallRuleProtocol(source.Protocol) hcloudFirewallRule.Port = source.Port hcloudFirewallRule.Description = source.Description @@ -2298,14 +2169,12 @@ func (c *converterImpl) schemaFromFloatingIPTypeLocationPricing(source FloatingI func (c *converterImpl) schemaFromFloatingIPTypePricing(source FloatingIPTypePricing) schema.PricingFloatingIPType { var schemaPricingFloatingIPType schema.PricingFloatingIPType schemaPricingFloatingIPType.Type = string(source.Type) - var schemaPricingFloatingIPTypePriceList []schema.PricingFloatingIPTypePrice if source.Pricings != nil { - schemaPricingFloatingIPTypePriceList = make([]schema.PricingFloatingIPTypePrice, len(source.Pricings)) + schemaPricingFloatingIPType.Prices = make([]schema.PricingFloatingIPTypePrice, len(source.Pricings)) for i := 0; i < len(source.Pricings); i++ { - schemaPricingFloatingIPTypePriceList[i] = c.schemaFromFloatingIPTypeLocationPricing(source.Pricings[i]) + schemaPricingFloatingIPType.Prices[i] = c.schemaFromFloatingIPTypeLocationPricing(source.Pricings[i]) } } - schemaPricingFloatingIPType.Prices = schemaPricingFloatingIPTypePriceList return schemaPricingFloatingIPType } func (c *converterImpl) schemaFromImagePricing(source ImagePricing) schema.PricingImage { @@ -2319,41 +2188,33 @@ func (c *converterImpl) schemaFromLoadBalancerTypePricing(source LoadBalancerTyp if source.LoadBalancerType != nil { pInt64 = &source.LoadBalancerType.ID } - var xint64 int64 if pInt64 != nil { - xint64 = *pInt64 + schemaPricingLoadBalancerType.ID = *pInt64 } - schemaPricingLoadBalancerType.ID = xint64 var pString *string if source.LoadBalancerType != nil { pString = &source.LoadBalancerType.Name } - var xstring string if pString != nil { - xstring = *pString + schemaPricingLoadBalancerType.Name = *pString } - schemaPricingLoadBalancerType.Name = xstring - var schemaPricingLoadBalancerTypePriceList []schema.PricingLoadBalancerTypePrice if source.Pricings != nil { - schemaPricingLoadBalancerTypePriceList = make([]schema.PricingLoadBalancerTypePrice, len(source.Pricings)) + schemaPricingLoadBalancerType.Prices = make([]schema.PricingLoadBalancerTypePrice, len(source.Pricings)) for i := 0; i < len(source.Pricings); i++ { - schemaPricingLoadBalancerTypePriceList[i] = c.SchemaFromLoadBalancerTypeLocationPricing(source.Pricings[i]) + schemaPricingLoadBalancerType.Prices[i] = c.SchemaFromLoadBalancerTypeLocationPricing(source.Pricings[i]) } } - schemaPricingLoadBalancerType.Prices = schemaPricingLoadBalancerTypePriceList return schemaPricingLoadBalancerType } func (c *converterImpl) schemaFromPrimaryIPPricing(source PrimaryIPPricing) schema.PricingPrimaryIP { var schemaPricingPrimaryIP schema.PricingPrimaryIP schemaPricingPrimaryIP.Type = source.Type - var schemaPricingPrimaryIPTypePriceList []schema.PricingPrimaryIPTypePrice if source.Pricings != nil { - schemaPricingPrimaryIPTypePriceList = make([]schema.PricingPrimaryIPTypePrice, len(source.Pricings)) + schemaPricingPrimaryIP.Prices = make([]schema.PricingPrimaryIPTypePrice, len(source.Pricings)) for i := 0; i < len(source.Pricings); i++ { - schemaPricingPrimaryIPTypePriceList[i] = c.schemaFromPrimaryIPTypePricing(source.Pricings[i]) + schemaPricingPrimaryIP.Prices[i] = c.schemaFromPrimaryIPTypePricing(source.Pricings[i]) } } - schemaPricingPrimaryIP.Prices = schemaPricingPrimaryIPTypePriceList return schemaPricingPrimaryIP } func (c *converterImpl) schemaFromPrimaryIPTypePricing(source PrimaryIPTypePricing) schema.PricingPrimaryIPTypePrice { @@ -2369,6 +2230,8 @@ func (c *converterImpl) schemaFromServerTypeLocationPricing(source ServerTypeLoc schemaPricingServerTypePrice.Location = c.pHcloudLocationToString(source.Location) schemaPricingServerTypePrice.PriceHourly = c.hcloudPriceToSchemaPrice(source.Hourly) schemaPricingServerTypePrice.PriceMonthly = c.hcloudPriceToSchemaPrice(source.Monthly) + schemaPricingServerTypePrice.IncludedTraffic = source.IncludedTraffic + schemaPricingServerTypePrice.PricePerTBTraffic = c.hcloudPriceToSchemaPrice(source.PerTBTraffic) return schemaPricingServerTypePrice } func (c *converterImpl) schemaFromServerTypePricing(source ServerTypePricing) schema.PricingServerType { @@ -2377,28 +2240,22 @@ func (c *converterImpl) schemaFromServerTypePricing(source ServerTypePricing) sc if source.ServerType != nil { pInt64 = &source.ServerType.ID } - var xint64 int64 if pInt64 != nil { - xint64 = *pInt64 + schemaPricingServerType.ID = *pInt64 } - schemaPricingServerType.ID = xint64 var pString *string if source.ServerType != nil { pString = &source.ServerType.Name } - var xstring string if pString != nil { - xstring = *pString + schemaPricingServerType.Name = *pString } - schemaPricingServerType.Name = xstring - var schemaPricingServerTypePriceList []schema.PricingServerTypePrice if source.Pricings != nil { - schemaPricingServerTypePriceList = make([]schema.PricingServerTypePrice, len(source.Pricings)) + schemaPricingServerType.Prices = make([]schema.PricingServerTypePrice, len(source.Pricings)) for i := 0; i < len(source.Pricings); i++ { - schemaPricingServerTypePriceList[i] = c.schemaFromServerTypeLocationPricing(source.Pricings[i]) + schemaPricingServerType.Prices[i] = c.schemaFromServerTypeLocationPricing(source.Pricings[i]) } } - schemaPricingServerType.Prices = schemaPricingServerTypePriceList return schemaPricingServerType } func (c *converterImpl) schemaFromTrafficPricing(source TrafficPricing) schema.PricingTraffic { @@ -2484,6 +2341,8 @@ func (c *converterImpl) serverTypePricingFromSchema(source schema.PricingServerT hcloudServerTypeLocationPricing.Location = &hcloudLocation hcloudServerTypeLocationPricing.Hourly = c.PriceFromSchema(source.PriceHourly) hcloudServerTypeLocationPricing.Monthly = c.PriceFromSchema(source.PriceMonthly) + hcloudServerTypeLocationPricing.IncludedTraffic = source.IncludedTraffic + hcloudServerTypeLocationPricing.PerTBTraffic = c.PriceFromSchema(source.PricePerTBTraffic) return hcloudServerTypeLocationPricing } func (c *converterImpl) timeTimeToTimeTime(source time.Time) time.Time { diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_server_client_iface.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_server_client_iface.go index 82038a30cbacb28023630ac51d69aaf60e48a72a..f249f7831bde5a147149e8af9fd2f47998bdcf6e 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_server_client_iface.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/zz_server_client_iface.go @@ -62,9 +62,8 @@ type IServerClient interface { AttachISO(ctx context.Context, server *Server, iso *ISO) (*Action, *Response, error) // DetachISO detaches the currently attached ISO from a server. DetachISO(ctx context.Context, server *Server) (*Action, *Response, error) - // EnableBackup enables backup for a server. Pass in an empty backup window to let the - // API pick a window for you. See the API documentation at docs.hetzner.cloud for a list - // of valid backup windows. + // EnableBackup enables backup for a server. + // The window parameter is deprecated and will be ignored. EnableBackup(ctx context.Context, server *Server, window string) (*Action, *Response, error) // DisableBackup disables backup for a server. DisableBackup(ctx context.Context, server *Server) (*Action, *Response, error) diff --git a/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go b/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go index d546afab12230099e473143e463a5675625cd537..a1ba45088c80c7182496aa6a3cde80291457a9bd 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go @@ -95,7 +95,9 @@ func newManager() (*hetznerManager, error) { hcloud.WithToken(token), hcloud.WithHTTPClient(httpClient), hcloud.WithApplication("cluster-autoscaler", version.ClusterAutoscalerVersion), - hcloud.WithPollBackoffFunc(hcloud.ExponentialBackoff(2, 500*time.Millisecond)), + hcloud.WithPollOpts(hcloud.PollOpts{ + BackoffFunc: hcloud.ExponentialBackoff(2, 500*time.Millisecond), + }), hcloud.WithDebugWriter(&debugWriter{}), } @@ -252,7 +254,7 @@ func (m *hetznerManager) deleteByNode(node *apiv1.Node) error { } func (m *hetznerManager) deleteServer(server *hcloud.Server) error { - _, err := m.client.Server.Delete(m.apiCallContext, server) + _, _, err := m.client.Server.DeleteWithResult(m.apiCallContext, server) return err }