package govultr import ( "context" "fmt" "net/http" "github.com/google/go-querystring/query" ) const vkePath = "/v2/kubernetes/clusters" // KubernetesService is the interface to interact with kubernetes endpoint on the Vultr API // Link : https://www.vultr.com/api/#tag/kubernetes type KubernetesService interface { CreateCluster(ctx context.Context, createReq *ClusterReq) (*Cluster, error) GetCluster(ctx context.Context, id string) (*Cluster, error) ListClusters(ctx context.Context, options *ListOptions) ([]Cluster, *Meta, error) UpdateCluster(ctx context.Context, vkeID string, updateReq *ClusterReqUpdate) error DeleteCluster(ctx context.Context, id string) error DeleteClusterWithResources(ctx context.Context, id string) error CreateNodePool(ctx context.Context, vkeID string, nodePoolReq *NodePoolReq) (*NodePool, error) ListNodePools(ctx context.Context, vkeID string, options *ListOptions) ([]NodePool, *Meta, error) GetNodePool(ctx context.Context, vkeID, nodePoolID string) (*NodePool, error) UpdateNodePool(ctx context.Context, vkeID, nodePoolID string, updateReq *NodePoolReqUpdate) (*NodePool, error) DeleteNodePool(ctx context.Context, vkeID, nodePoolID string) error DeleteNodePoolInstance(ctx context.Context, vkeID, nodePoolID, nodeID string) error RecycleNodePoolInstance(ctx context.Context, vkeID, nodePoolID, nodeID string) error GetKubeConfig(ctx context.Context, vkeID string) (*KubeConfig, error) GetVersions(ctx context.Context) (*Versions, error) GetUpgrades(ctx context.Context, vkeID string) ([]string, error) Upgrade(ctx context.Context, vkeID string, body *ClusterUpgradeReq) error } // KubernetesHandler handles interaction with the kubernetes methods for the Vultr API type KubernetesHandler struct { client *Client } // Cluster represents a full VKE cluster type Cluster struct { ID string `json:"id"` Label string `json:"label"` DateCreated string `json:"date_created"` ClusterSubnet string `json:"cluster_subnet"` ServiceSubnet string `json:"service_subnet"` IP string `json:"ip"` Endpoint string `json:"endpoint"` Version string `json:"version"` Region string `json:"region"` Status string `json:"status"` NodePools []NodePool `json:"node_pools"` } // NodePool represents a pool of nodes that are grouped by their label and plan type type NodePool struct { ID string `json:"id"` DateCreated string `json:"date_created"` DateUpdated string `json:"date_updated"` Label string `json:"label"` Plan string `json:"plan"` Status string `json:"status"` NodeQuantity int `json:"node_quantity"` MinNodes int `json:"min_nodes"` MaxNodes int `json:"max_nodes"` AutoScaler bool `json:"auto_scaler"` Tag string `json:"tag"` Nodes []Node `json:"nodes"` } // Node represents a node that will live within a nodepool type Node struct { ID string `json:"id"` DateCreated string `json:"date_created"` Label string `json:"label"` Status string `json:"status"` } // KubeConfig will contain the kubeconfig b64 encoded type KubeConfig struct { KubeConfig string `json:"kube_config"` } // ClusterReq struct used to create a cluster type ClusterReq struct { Label string `json:"label"` Region string `json:"region"` Version string `json:"version"` NodePools []NodePoolReq `json:"node_pools"` } // ClusterReqUpdate struct used to update update a cluster type ClusterReqUpdate struct { Label string `json:"label"` } // NodePoolReq struct used to create a node pool type NodePoolReq struct { NodeQuantity int `json:"node_quantity"` Label string `json:"label"` Plan string `json:"plan"` Tag string `json:"tag"` MinNodes int `json:"min_nodes,omitempty"` MaxNodes int `json:"max_nodes,omitempty"` AutoScaler *bool `json:"auto_scaler"` } // NodePoolReqUpdate struct used to update a node pool type NodePoolReqUpdate struct { NodeQuantity int `json:"node_quantity,omitempty"` Tag *string `json:"tag,omitempty"` MinNodes int `json:"min_nodes,omitempty"` MaxNodes int `json:"max_nodes,omitempty"` AutoScaler *bool `json:"auto_scaler,omitempty"` } type vkeClustersBase struct { VKEClusters []Cluster `json:"vke_clusters"` Meta *Meta `json:"meta"` } type vkeClusterBase struct { VKECluster *Cluster `json:"vke_cluster"` } type vkeNodePoolsBase struct { NodePools []NodePool `json:"node_pools"` Meta *Meta `json:"meta"` } type vkeNodePoolBase struct { NodePool *NodePool `json:"node_pool"` } // Versions that are supported for VKE type Versions struct { Versions []string `json:"versions"` } // AvailableUpgrades for a given VKE cluster type availableUpgrades struct { AvailableUpgrades []string `json:"available_upgrades"` } // ClusterUpgradeReq struct for vke upgradse type ClusterUpgradeReq struct { UpgradeVersion string `json:"upgrade_version,omitempty"` } // CreateCluster will create a Kubernetes cluster. func (k *KubernetesHandler) CreateCluster(ctx context.Context, createReq *ClusterReq) (*Cluster, error) { req, err := k.client.NewRequest(ctx, http.MethodPost, vkePath, createReq) if err != nil { return nil, err } var k8 = new(vkeClusterBase) if err = k.client.DoWithContext(ctx, req, &k8); err != nil { return nil, err } return k8.VKECluster, nil } // GetCluster will return a Kubernetes cluster. func (k *KubernetesHandler) GetCluster(ctx context.Context, id string) (*Cluster, error) { req, err := k.client.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s/%s", vkePath, id), nil) if err != nil { return nil, err } k8 := new(vkeClusterBase) if err = k.client.DoWithContext(ctx, req, &k8); err != nil { return nil, err } return k8.VKECluster, nil } // ListClusters will return all kubernetes clusters. func (k *KubernetesHandler) ListClusters(ctx context.Context, options *ListOptions) ([]Cluster, *Meta, error) { req, err := k.client.NewRequest(ctx, http.MethodGet, vkePath, nil) if err != nil { return nil, nil, err } newValues, err := query.Values(options) if err != nil { return nil, nil, err } req.URL.RawQuery = newValues.Encode() k8s := new(vkeClustersBase) if err = k.client.DoWithContext(ctx, req, &k8s); err != nil { return nil, nil, err } return k8s.VKEClusters, k8s.Meta, nil } // UpdateCluster updates label on VKE cluster func (k *KubernetesHandler) UpdateCluster(ctx context.Context, vkeID string, updateReq *ClusterReqUpdate) error { req, err := k.client.NewRequest(ctx, http.MethodPut, fmt.Sprintf("%s/%s", vkePath, vkeID), updateReq) if err != nil { return err } return k.client.DoWithContext(ctx, req, nil) } // DeleteCluster will delete a Kubernetes cluster. func (k *KubernetesHandler) DeleteCluster(ctx context.Context, id string) error { req, err := k.client.NewRequest(ctx, http.MethodDelete, fmt.Sprintf("%s/%s", vkePath, id), nil) if err != nil { return err } return k.client.DoWithContext(ctx, req, nil) } // DeleteClusterWithResources will delete a Kubernetes cluster and all related resources. func (k *KubernetesHandler) DeleteClusterWithResources(ctx context.Context, id string) error { req, err := k.client.NewRequest(ctx, http.MethodDelete, fmt.Sprintf("%s/%s/delete-with-linked-resources", vkePath, id), nil) if err != nil { return err } return k.client.DoWithContext(ctx, req, nil) } // CreateNodePool creates a nodepool on a VKE cluster func (k *KubernetesHandler) CreateNodePool(ctx context.Context, vkeID string, nodePoolReq *NodePoolReq) (*NodePool, error) { req, err := k.client.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/%s/node-pools", vkePath, vkeID), nodePoolReq) if err != nil { return nil, err } n := new(vkeNodePoolBase) err = k.client.DoWithContext(ctx, req, n) if err != nil { return nil, err } return n.NodePool, nil } // ListNodePools will return all nodepools for a given VKE cluster func (k *KubernetesHandler) ListNodePools(ctx context.Context, vkeID string, options *ListOptions) ([]NodePool, *Meta, error) { req, err := k.client.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s/%s/node-pools", vkePath, vkeID), nil) if err != nil { return nil, nil, err } newValues, err := query.Values(options) if err != nil { return nil, nil, err } req.URL.RawQuery = newValues.Encode() n := new(vkeNodePoolsBase) if err = k.client.DoWithContext(ctx, req, &n); err != nil { return nil, nil, err } return n.NodePools, n.Meta, nil } // GetNodePool will return a single nodepool func (k *KubernetesHandler) GetNodePool(ctx context.Context, vkeID, nodePoolID string) (*NodePool, error) { req, err := k.client.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s/%s/node-pools/%s", vkePath, vkeID, nodePoolID), nil) if err != nil { return nil, err } n := new(vkeNodePoolBase) if err = k.client.DoWithContext(ctx, req, &n); err != nil { return nil, err } return n.NodePool, nil } // UpdateNodePool will allow you change the quantity of nodes within a nodepool func (k *KubernetesHandler) UpdateNodePool(ctx context.Context, vkeID, nodePoolID string, updateReq *NodePoolReqUpdate) (*NodePool, error) { req, err := k.client.NewRequest(ctx, http.MethodPatch, fmt.Sprintf("%s/%s/node-pools/%s", vkePath, vkeID, nodePoolID), updateReq) if err != nil { return nil, err } np := new(vkeNodePoolBase) if err = k.client.DoWithContext(ctx, req, np); err != nil { return nil, err } return np.NodePool, nil } // DeleteNodePool will remove a nodepool from a VKE cluster func (k *KubernetesHandler) DeleteNodePool(ctx context.Context, vkeID, nodePoolID string) error { req, err := k.client.NewRequest(ctx, http.MethodDelete, fmt.Sprintf("%s/%s/node-pools/%s", vkePath, vkeID, nodePoolID), nil) if err != nil { return err } return k.client.DoWithContext(ctx, req, nil) } // DeleteNodePoolInstance will remove a specified node from a nodepool func (k *KubernetesHandler) DeleteNodePoolInstance(ctx context.Context, vkeID, nodePoolID, nodeID string) error { req, err := k.client.NewRequest(ctx, http.MethodDelete, fmt.Sprintf("%s/%s/node-pools/%s/nodes/%s", vkePath, vkeID, nodePoolID, nodeID), nil) if err != nil { return err } return k.client.DoWithContext(ctx, req, nil) } // RecycleNodePoolInstance will recycle (destroy + redeploy) a given node on a nodepool func (k *KubernetesHandler) RecycleNodePoolInstance(ctx context.Context, vkeID, nodePoolID, nodeID string) error { req, err := k.client.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/%s/node-pools/%s/nodes/%s/recycle", vkePath, vkeID, nodePoolID, nodeID), nil) if err != nil { return err } return k.client.DoWithContext(ctx, req, nil) } // GetKubeConfig returns the kubeconfig for the specified VKE cluster func (k *KubernetesHandler) GetKubeConfig(ctx context.Context, vkeID string) (*KubeConfig, error) { req, err := k.client.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s/%s/config", vkePath, vkeID), nil) if err != nil { return nil, err } kc := new(KubeConfig) if err = k.client.DoWithContext(ctx, req, &kc); err != nil { return nil, err } return kc, nil } // GetVersions returns the supported kubernetes versions func (k *KubernetesHandler) GetVersions(ctx context.Context) (*Versions, error) { uri := "/v2/kubernetes/versions" req, err := k.client.NewRequest(ctx, http.MethodGet, uri, nil) if err != nil { return nil, err } versions := new(Versions) if err = k.client.DoWithContext(ctx, req, &versions); err != nil { return nil, err } return versions, nil } // GetUpgrades returns all version a VKE cluster can upgrade to func (k *KubernetesHandler) GetUpgrades(ctx context.Context, vkeID string) ([]string, error) { req, err := k.client.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s/%s/available-upgrades", vkePath, vkeID), nil) if err != nil { return nil, err } upgrades := new(availableUpgrades) if err = k.client.DoWithContext(ctx, req, &upgrades); err != nil { return nil, err } return upgrades.AvailableUpgrades, nil } // Upgrade beings a VKE cluster upgrade func (k *KubernetesHandler) Upgrade(ctx context.Context, vkeID string, body *ClusterUpgradeReq) error { req, err := k.client.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/%s/upgrades", vkePath, vkeID), body) if err != nil { return err } return k.client.DoWithContext(ctx, req, nil) }