diff --git a/cluster-autoscaler/cloudprovider/hetzner/README.md b/cluster-autoscaler/cloudprovider/hetzner/README.md index 5fbad46a8787..765636c441c7 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/README.md +++ b/cluster-autoscaler/cloudprovider/hetzner/README.md @@ -16,6 +16,10 @@ The cluster autoscaler for Hetzner Cloud scales worker nodes. `HCLOUD_SSH_KEY` Default empty , This SSH Key will have access to the fresh created server, @see https://docs.hetzner.cloud/#ssh-keys +`HCLOUD_PUBLIC_IPV4` Default true , Whether the server is created with a public IPv4 address or not, @see https://docs.hetzner.cloud/#primary-ips + +`HCLOUD_PUBLIC_IPV6` Default true , Whether the server is created with a public IPv6 address or not, @see https://docs.hetzner.cloud/#primary-ips + Node groups must be defined with the `--nodes=::::` flag. Multiple flags will create multiple node pools. For example: diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/certificate.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/certificate.go index 975eb0951f37..67ce802641c4 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/certificate.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/certificate.go @@ -154,6 +154,7 @@ func (c *CertificateClient) Get(ctx context.Context, idOrName string) (*Certific type CertificateListOpts struct { ListOpts Name string + Sort []string } func (l CertificateListOpts) values() url.Values { @@ -161,6 +162,9 @@ func (l CertificateListOpts) values() url.Values { if l.Name != "" { vals.Add("name", l.Name) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go index b85cd4617ed8..c36cf605cb9b 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/client.go @@ -20,6 +20,7 @@ import ( "bytes" "context" "encoding/json" + "errors" "fmt" "io" "io/ioutil" @@ -31,10 +32,9 @@ import ( "strings" "time" - "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/internal/instrumentation" - "github.com/prometheus/client_golang/prometheus" - + "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" ) @@ -70,6 +70,7 @@ func ExponentialBackoff(b float64, d time.Duration) BackoffFunc { type Client struct { endpoint string token string + tokenValid bool pollInterval time.Duration backoffFunc BackoffFunc httpClient *http.Client @@ -97,6 +98,7 @@ type Client struct { Volume VolumeClient PlacementGroup PlacementGroupClient RDNS RDNSClient + PrimaryIP PrimaryIPClient } // A ClientOption is used to configure a Client. @@ -113,6 +115,7 @@ func WithEndpoint(endpoint string) ClientOption { func WithToken(token string) ClientOption { return func(client *Client) { client.token = token + client.tokenValid = httpguts.ValidHeaderFieldValue(token) } } @@ -167,6 +170,7 @@ func WithInstrumentation(registry *prometheus.Registry) ClientOption { func NewClient(options ...ClientOption) *Client { client := &Client{ endpoint: Endpoint, + tokenValid: true, httpClient: &http.Client{}, backoffFunc: ExponentialBackoff(2, 500*time.Millisecond), pollInterval: 500 * time.Millisecond, @@ -200,6 +204,7 @@ func NewClient(options ...ClientOption) *Client { client.Firewall = FirewallClient{client: client} client.PlacementGroup = PlacementGroupClient{client: client} client.RDNS = RDNSClient{client: client} + client.PrimaryIP = PrimaryIPClient{client: client} return client } @@ -213,9 +218,13 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re return nil, err } req.Header.Set("User-Agent", c.userAgent) - if c.token != "" { + + if !c.tokenValid { + return nil, errors.New("Authorization token contains invalid characters") + } else if c.token != "" { req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.token)) } + if body != nil { req.Header.Set("Content-Type", "application/json") } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/datacenter.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/datacenter.go index 05f31d6b0cb7..514771b54479 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/datacenter.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/datacenter.go @@ -88,6 +88,7 @@ func (c *DatacenterClient) Get(ctx context.Context, idOrName string) (*Datacente type DatacenterListOpts struct { ListOpts Name string + Sort []string } func (l DatacenterListOpts) values() url.Values { @@ -95,6 +96,9 @@ func (l DatacenterListOpts) values() url.Values { if l.Name != "" { vals.Add("name", l.Name) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/error.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/error.go index 49407e574801..6f983bede7d6 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/error.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/error.go @@ -41,7 +41,8 @@ const ( ErrorCodeMaintenance ErrorCode = "maintenance" // Cannot perform operation due to maintenance ErrorCodeConflict ErrorCode = "conflict" // The resource has changed during the request, please retry ErrorCodeRobotUnavailable ErrorCode = "robot_unavailable" // Robot was not available. The caller may retry the operation after a short delay - ErrorUnsupportedError ErrorCode = "unsupported_error" // The gives resource does not support this + ErrorCodeResourceLocked ErrorCode = "resource_locked" // The resource is locked. The caller should contact support + ErrorUnsupportedError ErrorCode = "unsupported_error" // The given resource does not support this // Server related error codes ErrorCodeInvalidServerType ErrorCode = "invalid_server_type" // The server type does not fit for the given server or is deprecated diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/firewall.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/firewall.go index b69b734c385c..4734bb4f6271 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/firewall.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/firewall.go @@ -152,6 +152,7 @@ func (c *FirewallClient) Get(ctx context.Context, idOrName string) (*Firewall, * type FirewallListOpts struct { ListOpts Name string + Sort []string } func (l FirewallListOpts) values() url.Values { @@ -159,6 +160,9 @@ func (l FirewallListOpts) values() url.Values { if l.Name != "" { vals.Add("name", l.Name) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } 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 ad86d7c242f2..8be0915ef5ac 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/floating_ip.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/floating_ip.go @@ -153,6 +153,7 @@ func (c *FloatingIPClient) Get(ctx context.Context, idOrName string) (*FloatingI type FloatingIPListOpts struct { ListOpts Name string + Sort []string } func (l FloatingIPListOpts) values() url.Values { @@ -160,6 +161,9 @@ func (l FloatingIPListOpts) values() url.Values { if l.Name != "" { vals.Add("name", l.Name) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/hcloud.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/hcloud.go index ac2d5cb28e38..332ff7496742 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/hcloud.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/hcloud.go @@ -18,4 +18,4 @@ limitations under the License. package hcloud // Version is the library's version following Semantic Versioning. -const Version = "1.32.0" +const Version = "1.35.0" diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/iso.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/iso.go index 57679be585bc..1fece1e559ff 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/iso.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/iso.go @@ -98,6 +98,7 @@ func (c *ISOClient) Get(ctx context.Context, idOrName string) (*ISO, *Response, type ISOListOpts struct { ListOpts Name string + Sort []string } func (l ISOListOpts) values() url.Values { @@ -105,6 +106,9 @@ func (l ISOListOpts) values() url.Values { if l.Name != "" { vals.Add("name", l.Name) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/labels.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/labels.go new file mode 100644 index 000000000000..a8c3b4fe81ab --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/labels.go @@ -0,0 +1,23 @@ +package hcloud + +import ( + "fmt" + "regexp" +) + +var keyRegexp = regexp.MustCompile( + `^([a-z0-9A-Z]((?:[\-_.]|[a-z0-9A-Z]){0,253}[a-z0-9A-Z])?/)?[a-z0-9A-Z]((?:[\-_.]|[a-z0-9A-Z]|){0,62}[a-z0-9A-Z])?$`) +var valueRegexp = regexp.MustCompile(`^([a-z0-9A-Z](?:[\-_.]|[a-z0-9A-Z]){0,62})?[a-z0-9A-Z]$`) + +func ValidateResourceLabels(labels map[string]interface{}) (bool, error) { + for k, v := range labels { + if match := keyRegexp.MatchString(k); !match { + return false, fmt.Errorf("label key '%s' is not correctly formatted", k) + } + + if match := valueRegexp.MatchString(v.(string)); !match { + return false, fmt.Errorf("label value '%s' (key: %s) is not correctly formatted", v, k) + } + } + return true, 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 c6eadb717729..e17d1aeef411 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/load_balancer.go @@ -299,6 +299,7 @@ func (c *LoadBalancerClient) Get(ctx context.Context, idOrName string) (*LoadBal type LoadBalancerListOpts struct { ListOpts Name string + Sort []string } func (l LoadBalancerListOpts) values() url.Values { @@ -306,6 +307,9 @@ func (l LoadBalancerListOpts) values() url.Values { if l.Name != "" { vals.Add("name", l.Name) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } 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 4be70e072d78..ea9ea9556ebc 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 @@ -85,6 +85,7 @@ func (c *LoadBalancerTypeClient) Get(ctx context.Context, idOrName string) (*Loa type LoadBalancerTypeListOpts struct { ListOpts Name string + Sort []string } func (l LoadBalancerTypeListOpts) values() url.Values { @@ -92,6 +93,9 @@ func (l LoadBalancerTypeListOpts) values() url.Values { if l.Name != "" { vals.Add("name", l.Name) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/location.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/location.go index 8d0838da842b..fad81fc6b223 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/location.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/location.go @@ -85,6 +85,7 @@ func (c *LocationClient) Get(ctx context.Context, idOrName string) (*Location, * type LocationListOpts struct { ListOpts Name string + Sort []string } func (l LocationListOpts) values() url.Values { @@ -92,6 +93,9 @@ func (l LocationListOpts) values() url.Values { if l.Name != "" { vals.Add("name", l.Name) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } 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 e96577f76e1d..f3f66531c26d 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/metadata/client.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/metadata/client.go @@ -17,6 +17,7 @@ limitations under the License. package metadata import ( + "fmt" "io/ioutil" "net" "net/http" @@ -88,12 +89,16 @@ func (c *Client) get(path string) (string, error) { if err != nil { return "", err } - body, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return "", err } - resp.Body.Close() - return string(body), nil + body := string(bodyBytes) + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return body, fmt.Errorf("response status was %d", resp.StatusCode) + } + return body, nil } // IsHcloudServer checks if the currently called server is a hcloud server by calling a metadata endpoint diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/network.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/network.go index 25bc67713ad3..e95c92c6141d 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/network.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/network.go @@ -130,6 +130,7 @@ func (c *NetworkClient) Get(ctx context.Context, idOrName string) (*Network, *Re type NetworkListOpts struct { ListOpts Name string + Sort []string } func (l NetworkListOpts) values() url.Values { @@ -137,6 +138,9 @@ func (l NetworkListOpts) values() url.Values { if l.Name != "" { vals.Add("name", l.Name) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } 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 9dbe6b87371f..b8c7fff361ff 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/placement_group.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/placement_group.go @@ -96,6 +96,7 @@ type PlacementGroupListOpts struct { ListOpts Name string Type PlacementGroupType + Sort []string } func (l PlacementGroupListOpts) values() url.Values { @@ -106,6 +107,9 @@ func (l PlacementGroupListOpts) values() url.Values { if l.Type != "" { vals.Add("type", string(l.Type)) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/pricing.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/pricing.go index ad2bcbe3bfd4..e047c38ccc03 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/pricing.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/pricing.go @@ -27,6 +27,7 @@ type Pricing struct { Image ImagePricing FloatingIP FloatingIPPricing FloatingIPs []FloatingIPTypePricing + PrimaryIPs []PrimaryIPPricing Traffic TrafficPricing ServerBackup ServerBackupPricing ServerTypes []ServerTypePricing @@ -44,6 +45,14 @@ type Price struct { Gross string } +// PrimaryIPPrice represents a price. Net amount and gross amount are +// specified as strings and it is the user's responsibility to convert them to +// appropriate types for calculations. +type PrimaryIPPrice struct { + Net string + Gross string +} + // ImagePricing provides pricing information for imaegs. type ImagePricing struct { PerGBMonth Price @@ -60,6 +69,20 @@ type FloatingIPTypePricing struct { Pricings []FloatingIPTypeLocationPricing } +// PrimaryIPTypePricing defines the schema of pricing information for a primary IP +// type at a datacenter. +type PrimaryIPTypePricing struct { + Datacenter string + Hourly PrimaryIPPrice + Monthly PrimaryIPPrice +} + +// PrimaryIPTypePricing provides pricing information for PrimaryIPs +type PrimaryIPPricing struct { + Type string + Pricings []PrimaryIPTypePricing +} + // FloatingIPTypeLocationPricing provides pricing information for a Floating IP type // at a location. type FloatingIPTypeLocationPricing struct { diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/primary_ip.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/primary_ip.go new file mode 100644 index 000000000000..c519001e1824 --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/primary_ip.go @@ -0,0 +1,399 @@ +package hcloud + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net" + "net/url" + "strconv" + "time" + + "k8s.io/autoscaler/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema" +) + +// PrimaryIP defines a Primary IP +type PrimaryIP struct { + ID int + IP net.IP + Network *net.IPNet + Labels map[string]string + Name string + Type PrimaryIPType + Protection PrimaryIPProtection + DNSPtr map[string]string + AssigneeID int + AssigneeType string + AutoDelete bool + Blocked bool + Created time.Time + Datacenter *Datacenter +} + +// PrimaryIPProtection represents the protection level of a Primary IP. +type PrimaryIPProtection struct { + Delete bool +} + +// PrimaryIPDNSPTR contains reverse DNS information for a +// IPv4 or IPv6 Primary IP. +type PrimaryIPDNSPTR struct { + DNSPtr string + IP string +} + +// GetDNSPtrForIP searches for the dns assigned to the given IP address. +// It returns an error if there is no dns set for the given IP address. +func (p *PrimaryIP) GetDNSPtrForIP(ip net.IP) (string, error) { + dns, ok := p.DNSPtr[ip.String()] + if !ok { + return "", DNSNotFoundError{ip} + } + + return dns, nil +} + +// PrimaryIPType represents the type of Primary IP. +type PrimaryIPType string + +// PrimaryIPType Primary IP types. +const ( + PrimaryIPTypeIPv4 PrimaryIPType = "ipv4" + PrimaryIPTypeIPv6 PrimaryIPType = "ipv6" +) + +// PrimaryIPCreateOpts defines the request to +// create a Primary IP. +type PrimaryIPCreateOpts struct { + AssigneeID *int `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"` +} + +// PrimaryIPCreateResult defines the response +// when creating a Primary IP. +type PrimaryIPCreateResult struct { + PrimaryIP *PrimaryIP + Action *Action +} + +// 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"` +} + +// PrimaryIPUpdateResult defines the response +// when updating a Primary IP. +type PrimaryIPUpdateResult struct { + PrimaryIP PrimaryIP `json:"primary_ip"` +} + +// PrimaryIPAssignOpts defines the request to +// assign a Primary IP to an assignee (usually a server). +type PrimaryIPAssignOpts struct { + ID int + AssigneeID int `json:"assignee_id"` + AssigneeType string `json:"assignee_type"` +} + +// PrimaryIPAssignResult defines the response +// when assigning a Primary IP to a assignee. +type PrimaryIPAssignResult struct { + Action schema.Action `json:"action"` +} + +// PrimaryIPChangeDNSPtrOpts defines the request to +// change a DNS PTR entry from a Primary IP +type PrimaryIPChangeDNSPtrOpts struct { + ID int + DNSPtr string `json:"dns_ptr"` + IP string `json:"ip"` +} + +// PrimaryIPChangeDNSPtrResult defines the response +// when assigning a Primary IP to a assignee. +type PrimaryIPChangeDNSPtrResult struct { + Action schema.Action `json:"action"` +} + +// PrimaryIPChangeProtectionOpts defines the request to +// change protection configuration of a Primary IP +type PrimaryIPChangeProtectionOpts struct { + ID int + Delete bool `json:"delete"` +} + +// PrimaryIPChangeProtectionResult defines the response +// when changing a protection of a PrimaryIP +type PrimaryIPChangeProtectionResult struct { + Action schema.Action `json:"action"` +} + +// PrimaryIPClient is a client for the Primary IP API +type PrimaryIPClient struct { + client *Client +} + +// 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 int) (*PrimaryIP, *Response, error) { + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/primary_ips/%d", id), nil) + if err != nil { + return nil, nil, err + } + + var body schema.PrimaryIPGetResult + resp, err := c.client.Do(req, &body) + if err != nil { + if IsError(err, ErrorCodeNotFound) { + return nil, resp, nil + } + return nil, nil, err + } + return PrimaryIPFromSchema(body.PrimaryIP), resp, nil +} + +// GetByIP retrieves a Primary IP by its IP Address. If the Primary IP does not exist, nil is returned. +func (c *PrimaryIPClient) GetByIP(ctx context.Context, ip string) (*PrimaryIP, *Response, error) { + 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 +} + +// 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 +} + +// 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.Atoi(idOrName); err == nil { + return c.GetByID(ctx, int(id)) + } + return c.GetByName(ctx, idOrName) +} + +// PrimaryIPListOpts specifies options for listing Primary IPs. +type PrimaryIPListOpts struct { + ListOpts + Name string + IP string + Sort []string +} + +func (l PrimaryIPListOpts) values() url.Values { + vals := l.ListOpts.values() + if l.Name != "" { + vals.Add("name", l.Name) + } + if l.IP != "" { + vals.Add("ip", l.IP) + } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } + return vals +} + +// List returns a list of Primary IPs for a specific page. +// +// 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 + } + + var body schema.PrimaryIPListResult + resp, err := c.client.Do(req, &body) + 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 primaryIPs, resp, nil +} + +// All returns all Primary IPs. +func (c *PrimaryIPClient) All(ctx context.Context) ([]*PrimaryIP, error) { + allPrimaryIPs := []*PrimaryIP{} + + opts := PrimaryIPListOpts{} + opts.PerPage = 50 + + err := c.client.all(func(page int) (*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 + }) + 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 + } + + req, err := c.client.NewRequest(ctx, "POST", "/primary_ips", bytes.NewReader(reqBodyData)) + if err != nil { + return &PrimaryIPCreateResult{}, nil, err + } + + var respBody schema.PrimaryIPCreateResponse + resp, err := c.client.Do(req, &respBody) + if err != nil { + return &PrimaryIPCreateResult{}, resp, err + } + var action *Action + if respBody.Action != nil { + action = ActionFromSchema(*respBody.Action) + } + primaryIP := PrimaryIPFromSchema(respBody.PrimaryIP) + return &PrimaryIPCreateResult{ + PrimaryIP: primaryIP, + Action: action, + }, 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) +} + +// 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 + } + + 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 + } + + respBody := PrimaryIPUpdateResult{} + resp, err := c.client.Do(req, &respBody) + if err != nil { + return nil, resp, err + } + return &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 + } + + 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 + } + + var respBody PrimaryIPAssignResult + resp, err := c.client.Do(req, &respBody) + 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 int) (*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 + } + + var respBody PrimaryIPAssignResult + resp, err := c.client.Do(req, &respBody) + 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 + } + + 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 + } + + var respBody PrimaryIPChangeDNSPtrResult + resp, err := c.client.Do(req, &respBody) + 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 + } + + 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 + } + + var respBody PrimaryIPChangeProtectionResult + resp, err := c.client.Do(req, &respBody) + 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 2baa4202b7ce..ba528204b655 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema.go @@ -98,6 +98,40 @@ func FloatingIPFromSchema(s schema.FloatingIP) *FloatingIP { return f } +// PrimaryIPFromSchema converts a schema.PrimaryIP to a PrimaryIP. +func PrimaryIPFromSchema(s schema.PrimaryIP) *PrimaryIP { + f := &PrimaryIP{ + ID: s.ID, + Type: PrimaryIPType(s.Type), + AutoDelete: s.AutoDelete, + + Created: s.Created, + Blocked: s.Blocked, + Protection: PrimaryIPProtection{ + Delete: s.Protection.Delete, + }, + Name: s.Name, + AssigneeType: s.AssigneeType, + AssigneeID: s.AssigneeID, + Datacenter: DatacenterFromSchema(s.Datacenter), + } + + if f.Type == PrimaryIPTypeIPv4 { + f.IP = net.ParseIP(s.IP) + } else { + f.IP, f.Network, _ = net.ParseCIDR(s.IP) + } + f.DNSPtr = map[string]string{} + for _, entry := range s.DNSPtr { + f.DNSPtr[entry.IP] = entry.DNSPtr + } + f.Labels = map[string]string{} + for key, value := range s.Labels { + f.Labels[key] = value + } + return f +} + // ISOFromSchema converts a schema.ISO to an ISO. func ISOFromSchema(s schema.ISO) *ISO { return &ISO{ @@ -217,6 +251,7 @@ func ServerPublicNetFromSchema(s schema.ServerPublicNet) ServerPublicNet { // a ServerPublicNetIPv4. func ServerPublicNetIPv4FromSchema(s schema.ServerPublicNetIPv4) ServerPublicNetIPv4 { return ServerPublicNetIPv4{ + ID: s.ID, IP: net.ParseIP(s.IP), Blocked: s.Blocked, DNSPtr: s.DNSPtr, @@ -227,6 +262,7 @@ func ServerPublicNetIPv4FromSchema(s schema.ServerPublicNetIPv4) ServerPublicNet // a ServerPublicNetIPv6. func ServerPublicNetIPv6FromSchema(s schema.ServerPublicNetIPv6) ServerPublicNetIPv6 { ipv6 := ServerPublicNetIPv6{ + ID: s.ID, Blocked: s.Blocked, DNSPtr: map[string]string{}, } @@ -697,6 +733,24 @@ func PricingFromSchema(s schema.Pricing) Pricing { } p.FloatingIPs = append(p.FloatingIPs, FloatingIPTypePricing{Type: FloatingIPType(floatingIPType.Type), Pricings: pricings}) } + for _, primaryIPType := range s.PrimaryIPs { + var pricings []PrimaryIPTypePricing + for _, price := range primaryIPType.Prices { + p := PrimaryIPTypePricing{ + Datacenter: price.Datacenter, + Monthly: PrimaryIPPrice{ + Net: price.PriceMonthly.Net, + Gross: price.PriceMonthly.Gross, + }, + Hourly: PrimaryIPPrice{ + Net: price.PriceHourly.Net, + Gross: price.PriceHourly.Gross, + }, + } + pricings = append(pricings, p) + } + p.PrimaryIPs = append(p.PrimaryIPs, PrimaryIPPricing{Type: primaryIPType.Type, Pricings: pricings}) + } for _, serverType := range s.ServerTypes { var pricings []ServerTypeLocationPricing for _, price := range serverType.Prices { @@ -858,7 +912,9 @@ func loadBalancerCreateOptsToSchema(opts LoadBalancerCreateOpts) schema.LoadBala req.Network = Int(opts.Network.ID) } for _, target := range opts.Targets { - schemaTarget := schema.LoadBalancerCreateRequestTarget{} + schemaTarget := schema.LoadBalancerCreateRequestTarget{ + UsePrivateIP: target.UsePrivateIP, + } switch target.Type { case LoadBalancerTargetTypeServer: schemaTarget.Type = string(LoadBalancerTargetTypeServer) 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 5e8b80c09630..76c08e29b3e2 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/pricing.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/pricing.go @@ -23,6 +23,7 @@ type Pricing struct { Image PricingImage `json:"image"` FloatingIP PricingFloatingIP `json:"floating_ip"` FloatingIPs []PricingFloatingIPType `json:"floating_ips"` + PrimaryIPs []PricingPrimaryIP `json:"primary_ips"` Traffic PricingTraffic `json:"traffic"` ServerBackup PricingServerBackup `json:"server_backup"` ServerTypes []PricingServerType `json:"server_types"` @@ -108,3 +109,17 @@ type PricingLoadBalancerTypePrice struct { type PricingGetResponse struct { Pricing Pricing `json:"pricing"` } + +// PricingPrimaryIPTypePrice defines the schema of pricing information for a primary IP +// type at a datacenter. +type PricingPrimaryIPTypePrice struct { + Datacenter string `json:"datacenter"` + PriceHourly Price `json:"price_hourly"` + PriceMonthly Price `json:"price_monthly"` +} + +// PricingPrimaryIP define the schema of pricing information for a primary IP at a datacenter +type PricingPrimaryIP struct { + Type string `json:"type"` + Prices []PricingPrimaryIPTypePrice `json:"prices"` +} 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 new file mode 100644 index 000000000000..b21e28b4a8b4 --- /dev/null +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/primary_ip.go @@ -0,0 +1,49 @@ +package schema + +import "time" + +// PrimaryIP defines a Primary IP +type PrimaryIP struct { + ID int `json:"id"` + IP string `json:"ip"` + Labels map[string]string `json:"labels"` + Name string `json:"name"` + Type string `json:"type"` + Protection PrimaryIPProtection `json:"protection"` + DNSPtr []PrimaryIPDNSPTR `json:"dns_ptr"` + AssigneeID int `json:"assignee_id"` + AssigneeType string `json:"assignee_type"` + AutoDelete bool `json:"auto_delete"` + Blocked bool `json:"blocked"` + Created time.Time `json:"created"` + Datacenter Datacenter `json:"datacenter"` +} + +// PrimaryIPProtection represents the protection level of a Primary IP. +type PrimaryIPProtection struct { + Delete bool `json:"delete"` +} + +// PrimaryIPDNSPTR contains reverse DNS information for a +// IPv4 or IPv6 Primary IP. +type PrimaryIPDNSPTR struct { + DNSPtr string `json:"dns_ptr"` + IP string `json:"ip"` +} + +// PrimaryIPCreateResponse defines the schema of the response +// when creating a Primary IP. +type PrimaryIPCreateResponse struct { + PrimaryIP PrimaryIP `json:"primary_ip"` + Action *Action `json:"action"` +} + +// PrimaryIPGetResult defines the response when retrieving a single Primary IP. +type PrimaryIPGetResult struct { + PrimaryIP PrimaryIP `json:"primary_ip"` +} + +// PrimaryIPListResult defines the response when listing Primary IPs. +type PrimaryIPListResult struct { + PrimaryIPs []PrimaryIP `json:"primary_ips"` +} 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 2ac327381512..4fc11a3ca02e 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/schema/server.go @@ -61,6 +61,7 @@ type ServerPublicNet struct { // ServerPublicNetIPv4 defines the schema of a server's public // network information for an IPv4. type ServerPublicNetIPv4 struct { + ID int `json:"id"` IP string `json:"ip"` Blocked bool `json:"blocked"` DNSPtr string `json:"dns_ptr"` @@ -69,6 +70,7 @@ type ServerPublicNetIPv4 struct { // ServerPublicNetIPv6 defines the schema of a server's public // network information for an IPv6. type ServerPublicNetIPv6 struct { + ID int `json:"id"` IP string `json:"ip"` Blocked bool `json:"blocked"` DNSPtr []ServerPublicNetIPv6DNSPtr `json:"dns_ptr"` @@ -125,9 +127,18 @@ type ServerCreateRequest struct { Networks []int `json:"networks,omitempty"` Firewalls []ServerCreateFirewalls `json:"firewalls,omitempty"` PlacementGroup int `json:"placement_group,omitempty"` + PublicNet *ServerCreatePublicNet `json:"public_net,omitempty"` } -// ServerCreateFirewall defines which Firewalls to apply when creating a Server. +// ServerCreatePublicNet defines the public network configuration of a server. +type ServerCreatePublicNet struct { + EnableIPv4 bool `json:"enable_ipv4"` + EnableIPv6 bool `json:"enable_ipv6"` + IPv4ID int `json:"ipv4,omitempty"` + IPv6ID int `json:"ipv6,omitempty"` +} + +// ServerCreateFirewalls defines which Firewalls to apply when creating a Server. type ServerCreateFirewalls struct { Firewall int `json:"firewall"` } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server.go index 79783fd94325..f06f7974565f 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server.go @@ -114,19 +114,29 @@ type ServerPublicNet struct { // ServerPublicNetIPv4 represents a server's public IPv4 address. type ServerPublicNetIPv4 struct { + ID int IP net.IP Blocked bool DNSPtr string } +func (n *ServerPublicNetIPv4) IsUnspecified() bool { + return n.IP == nil || n.IP.Equal(net.IPv4zero) +} + // ServerPublicNetIPv6 represents a Server's public IPv6 network and address. type ServerPublicNetIPv6 struct { + ID int IP net.IP Network *net.IPNet Blocked bool DNSPtr map[string]string } +func (n *ServerPublicNetIPv6) IsUnspecified() bool { + return n.IP == nil || n.IP.Equal(net.IPv6unspecified) +} + // ServerPrivateNet defines the schema of a Server's private network information. type ServerPrivateNet struct { Network *Network @@ -136,8 +146,8 @@ type ServerPrivateNet struct { } // DNSPtrForIP returns the reverse dns pointer of the ip address. -func (s *ServerPublicNetIPv6) DNSPtrForIP(ip net.IP) string { - return s.DNSPtr[ip.String()] +func (n *ServerPublicNetIPv6) DNSPtrForIP(ip net.IP) string { + return n.DNSPtr[ip.String()] } // ServerFirewallStatus represents a Firewall and its status on a Server's @@ -152,9 +162,8 @@ type ServerRescueType string // List of rescue types. const ( - ServerRescueTypeLinux32 ServerRescueType = "linux32" - ServerRescueTypeLinux64 ServerRescueType = "linux64" - ServerRescueTypeFreeBSD64 ServerRescueType = "freebsd64" + ServerRescueTypeLinux32 ServerRescueType = "linux32" + ServerRescueTypeLinux64 ServerRescueType = "linux64" ) // changeDNSPtr changes or resets the reverse DNS pointer for a IP address. @@ -244,6 +253,7 @@ type ServerListOpts struct { ListOpts Name string Status []ServerStatus + Sort []string } func (l ServerListOpts) values() url.Values { @@ -254,6 +264,9 @@ func (l ServerListOpts) values() url.Values { for _, status := range l.Status { vals.Add("status", string(status)) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } @@ -321,6 +334,14 @@ type ServerCreateOpts struct { Networks []*Network Firewalls []*ServerCreateFirewall PlacementGroup *PlacementGroup + PublicNet *ServerCreatePublicNet +} + +type ServerCreatePublicNet struct { + EnableIPv4 bool + EnableIPv6 bool + IPv4 *PrimaryIP + IPv6 *PrimaryIP } // ServerCreateFirewall defines which Firewalls to apply when creating a Server. @@ -342,6 +363,11 @@ func (o ServerCreateOpts) Validate() error { 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 { + return errors.New("missing networks when EnableIPv4 and EnableIPv6 is false") + } + } return nil } @@ -391,6 +417,19 @@ func (c *ServerClient) Create(ctx context.Context, opts ServerCreateOpts) (Serve Firewall: firewall.Firewall.ID, }) } + + if opts.PublicNet != nil { + reqBody.PublicNet = &schema.ServerCreatePublicNet{ + EnableIPv4: opts.PublicNet.EnableIPv4, + EnableIPv6: opts.PublicNet.EnableIPv6, + } + if opts.PublicNet.IPv4 != nil { + reqBody.PublicNet.IPv4ID = opts.PublicNet.IPv4.ID + } + if opts.PublicNet.IPv6 != nil { + reqBody.PublicNet.IPv6ID = opts.PublicNet.IPv6.ID + } + } if opts.Location != nil { if opts.Location.ID != 0 { reqBody.Location = strconv.Itoa(opts.Location.ID) 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 6234a9a30fcf..2f4ff1f04303 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server_type.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/server_type.go @@ -108,6 +108,7 @@ func (c *ServerTypeClient) Get(ctx context.Context, idOrName string) (*ServerTyp type ServerTypeListOpts struct { ListOpts Name string + Sort []string } func (l ServerTypeListOpts) values() url.Values { @@ -115,6 +116,9 @@ func (l ServerTypeListOpts) values() url.Values { if l.Name != "" { vals.Add("name", l.Name) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } 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 bba78de3dcad..92a980611a7c 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/ssh_key.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/ssh_key.go @@ -97,6 +97,7 @@ type SSHKeyListOpts struct { ListOpts Name string Fingerprint string + Sort []string } func (l SSHKeyListOpts) values() url.Values { @@ -107,6 +108,9 @@ func (l SSHKeyListOpts) values() url.Values { if l.Fingerprint != "" { vals.Add("fingerprint", l.Fingerprint) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/volume.go b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/volume.go index 03ed776e0f29..4b3426c7823f 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/volume.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hcloud-go/hcloud/volume.go @@ -108,6 +108,7 @@ type VolumeListOpts struct { ListOpts Name string Status []VolumeStatus + Sort []string } func (l VolumeListOpts) values() url.Values { @@ -118,6 +119,9 @@ func (l VolumeListOpts) values() url.Values { for _, status := range l.Status { vals.Add("status", string(status)) } + for _, sort := range l.Sort { + vals.Add("sort", sort) + } return vals } diff --git a/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go b/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go index 9cbf373df73b..f3d223c48c27 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hetzner_manager.go @@ -47,6 +47,8 @@ type hetznerManager struct { network *hcloud.Network firewall *hcloud.Firewall createTimeout time.Duration + publicIPv4 bool + publicIPv6 bool } func newManager() (*hetznerManager, error) { @@ -72,6 +74,24 @@ func newManager() (*hetznerManager, error) { imageName = "ubuntu-20.04" } + publicIPv4 := true + publicIPv4Str := os.Getenv("HCLOUD_PUBLIC_IPV4") + if publicIPv4Str != "" { + publicIPv4, err = strconv.ParseBool(publicIPv4Str) + if err != nil { + return nil, fmt.Errorf("failed to parse HCLOUD_PUBLIC_IPV4: %s", err) + } + } + + publicIPv6 := true + publicIPv6Str := os.Getenv("HCLOUD_PUBLIC_IPV6") + if publicIPv6Str != "" { + publicIPv6, err = strconv.ParseBool(publicIPv6Str) + if err != nil { + return nil, fmt.Errorf("failed to parse HCLOUD_PUBLIC_IPV6: %s", err) + } + } + // Search for an image ID corresponding to the supplied HCLOUD_IMAGE env // variable. This value can either be an image ID itself (an int), a name // (e.g. "ubuntu-20.04"), or a label selector associated with an image @@ -141,6 +161,8 @@ func newManager() (*hetznerManager, error) { firewall: firewall, createTimeout: createTimeout, apiCallContext: ctx, + publicIPv4: publicIPv4, + publicIPv6: publicIPv6, } m.nodeGroups[drainingNodePoolId] = &hetznerNodeGroup{ diff --git a/cluster-autoscaler/cloudprovider/hetzner/hetzner_node_group.go b/cluster-autoscaler/cloudprovider/hetzner/hetzner_node_group.go index c292109ca89e..b7fe9e7691de 100644 --- a/cluster-autoscaler/cloudprovider/hetzner/hetzner_node_group.go +++ b/cluster-autoscaler/cloudprovider/hetzner/hetzner_node_group.go @@ -358,6 +358,10 @@ func createServer(n *hetznerNodeGroup) error { Labels: map[string]string{ nodeGroupLabel: n.id, }, + PublicNet: &hcloud.ServerCreatePublicNet{ + EnableIPv4: n.manager.publicIPv4, + EnableIPv6: n.manager.publicIPv6, + }, } if n.manager.sshKey != nil { opts.SSHKeys = []*hcloud.SSHKey{n.manager.sshKey} diff --git a/cluster-autoscaler/go.mod b/cluster-autoscaler/go.mod index 7f951c17de1b..39ea96b6a0bf 100644 --- a/cluster-autoscaler/go.mod +++ b/cluster-autoscaler/go.mod @@ -27,6 +27,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.1 golang.org/x/crypto v0.0.0-20220214200702-86341886e292 + golang.org/x/net v0.0.0-20220225172249-27dd8689420f golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 google.golang.org/api v0.46.0 google.golang.org/grpc v1.42.0 diff --git a/cluster-autoscaler/vendor/modules.txt b/cluster-autoscaler/vendor/modules.txt index fe0a10608f9b..1f0c8a94f83e 100644 --- a/cluster-autoscaler/vendor/modules.txt +++ b/cluster-autoscaler/vendor/modules.txt @@ -658,6 +658,7 @@ golang.org/x/crypto/pkcs12 golang.org/x/crypto/pkcs12/internal/rc2 golang.org/x/crypto/salsa20/salsa # golang.org/x/net v0.0.0-20220225172249-27dd8689420f +## explicit golang.org/x/net/bpf golang.org/x/net/context golang.org/x/net/context/ctxhttp