kubernetes.go raw

   1  package govultr
   2  
   3  import (
   4  	"context"
   5  	"fmt"
   6  	"net/http"
   7  
   8  	"github.com/google/go-querystring/query"
   9  )
  10  
  11  const vkePath = "/v2/kubernetes/clusters"
  12  
  13  // KubernetesService is the interface to interact with kubernetes endpoint on the Vultr API
  14  // Link : https://www.vultr.com/api/#tag/kubernetes
  15  type KubernetesService interface {
  16  	CreateCluster(ctx context.Context, createReq *ClusterReq) (*Cluster, *http.Response, error)
  17  	GetCluster(ctx context.Context, id string) (*Cluster, *http.Response, error)
  18  	ListClusters(ctx context.Context, options *ListOptions) ([]Cluster, *Meta, *http.Response, error)
  19  	UpdateCluster(ctx context.Context, vkeID string, updateReq *ClusterReqUpdate) error
  20  	DeleteCluster(ctx context.Context, id string) error
  21  	DeleteClusterWithResources(ctx context.Context, id string) error
  22  
  23  	CreateNodePool(ctx context.Context, vkeID string, nodePoolReq *NodePoolReq) (*NodePool, *http.Response, error)
  24  	ListNodePools(ctx context.Context, vkeID string, options *ListOptions) ([]NodePool, *Meta, *http.Response, error)
  25  	GetNodePool(ctx context.Context, vkeID, nodePoolID string) (*NodePool, *http.Response, error)
  26  	UpdateNodePool(ctx context.Context, vkeID, nodePoolID string, updateReq *NodePoolReqUpdate) (*NodePool, *http.Response, error)
  27  	DeleteNodePool(ctx context.Context, vkeID, nodePoolID string) error
  28  
  29  	ListNodePoolLabels(ctx context.Context, vkeID, nodePoolID string) ([]NodePoolLabel, *http.Response, error)
  30  	CreateNodePoolLabel(ctx context.Context, vkeID string, nodePoolID string, nodePoolLabelReq *NodePoolLabelReq) (*NodePoolLabel, *http.Response, error) //nolint:lll
  31  	GetNodePoolLabel(ctx context.Context, vkeID string, nodePoolID string, nodePoolLabelID string) (*NodePoolLabel, *http.Response, error)
  32  	DeleteNodePoolLabel(ctx context.Context, vkeID string, nodePoolID string, nodePoolLabelID string) error
  33  
  34  	ListNodePoolTaints(ctx context.Context, vkeID, nodePoolID string) ([]NodePoolTaint, *http.Response, error)
  35  	CreateNodePoolTaint(ctx context.Context, vkeID string, nodePoolID string, nodePoolTaintReq *NodePoolTaintReq) (*NodePoolTaint, *http.Response, error) //nolint:lll
  36  	GetNodePoolTaint(ctx context.Context, vkeID string, nodePoolID string, nodePoolTaintID string) (*NodePoolTaint, *http.Response, error)
  37  	DeleteNodePoolTaint(ctx context.Context, vkeID string, nodePoolID string, nodePoolTaintID string) error
  38  
  39  	ListWorkerNodes(ctx context.Context, vkeID, nodePoolID string, options *ListOptions) ([]Node, *Meta, *http.Response, error)
  40  	DeleteNodePoolInstance(ctx context.Context, vkeID, nodePoolID, nodeID string) error
  41  	RecycleNodePoolInstance(ctx context.Context, vkeID, nodePoolID, nodeID string) error
  42  
  43  	GetKubeConfig(ctx context.Context, vkeID string) (*KubeConfig, *http.Response, error)
  44  	GetVersions(ctx context.Context) (*Versions, *http.Response, error)
  45  
  46  	GetUpgrades(ctx context.Context, vkeID string) ([]string, *http.Response, error)
  47  	Upgrade(ctx context.Context, vkeID string, body *ClusterUpgradeReq) error
  48  }
  49  
  50  // KubernetesHandler handles interaction with the kubernetes methods for the Vultr API
  51  type KubernetesHandler struct {
  52  	client *Client
  53  }
  54  
  55  // Cluster represents a full VKE cluster
  56  type Cluster struct {
  57  	ID              string     `json:"id"`
  58  	Label           string     `json:"label"`
  59  	DateCreated     string     `json:"date_created"`
  60  	ClusterSubnet   string     `json:"cluster_subnet"`
  61  	ServiceSubnet   string     `json:"service_subnet"`
  62  	IP              string     `json:"ip"`
  63  	Endpoint        string     `json:"endpoint"`
  64  	Version         string     `json:"version"`
  65  	Region          string     `json:"region"`
  66  	Status          string     `json:"status"`
  67  	HAControlPlanes bool       `json:"ha_controlplanes"`
  68  	FirewallGroupID string     `json:"firewall_group_id"`
  69  	NodePools       []NodePool `json:"node_pools"`
  70  }
  71  
  72  // Taint represents a kubernetes taint that can be applied to nodes in a node pool
  73  type Taint struct {
  74  	Key    string `json:"key"`
  75  	Value  string `json:"value"`
  76  	Effect string `json:"effect"`
  77  }
  78  
  79  // NodePool represents a pool of nodes that are grouped by their label and plan type
  80  type NodePool struct {
  81  	ID           string            `json:"id"`
  82  	DateCreated  string            `json:"date_created"`
  83  	DateUpdated  string            `json:"date_updated"`
  84  	Label        string            `json:"label"`
  85  	Plan         string            `json:"plan"`
  86  	Status       string            `json:"status"`
  87  	NodeQuantity int               `json:"node_quantity"`
  88  	MinNodes     int               `json:"min_nodes"`
  89  	MaxNodes     int               `json:"max_nodes"`
  90  	AutoScaler   bool              `json:"auto_scaler"`
  91  	UserData     string            `json:"user_data"`
  92  	Tag          string            `json:"tag"`
  93  	Labels       map[string]string `json:"labels"`
  94  	Taints       []Taint           `json:"taints"`
  95  	Nodes        []Node            `json:"nodes"`
  96  }
  97  
  98  // Node represents a node that will live within a nodepool
  99  type Node struct {
 100  	ID          string `json:"id"`
 101  	DateCreated string `json:"date_created"`
 102  	Label       string `json:"label"`
 103  	IP          string `json:"ip,omitempty"` // Optional, may not be present in older API responses
 104  	Status      string `json:"status"`
 105  }
 106  
 107  // KubeConfig will contain the kubeconfig b64 encoded
 108  type KubeConfig struct {
 109  	KubeConfig string `json:"kube_config"`
 110  }
 111  
 112  // ClusterReq struct used to create a cluster
 113  type ClusterReq struct {
 114  	Label           string        `json:"label"`
 115  	Region          string        `json:"region"`
 116  	Version         string        `json:"version"`
 117  	HAControlPlanes bool          `json:"ha_controlplanes,omitempty"`
 118  	EnableFirewall  bool          `json:"enable_firewall,omitempty"`
 119  	VPCID           string        `json:"vpc_id,omitempty"`
 120  	NodePools       []NodePoolReq `json:"node_pools"`
 121  }
 122  
 123  // ClusterReqUpdate struct used to update update a cluster
 124  type ClusterReqUpdate struct {
 125  	Label string `json:"label"`
 126  }
 127  
 128  // NodePoolReq struct used to create a node pool
 129  type NodePoolReq struct {
 130  	NodeQuantity int               `json:"node_quantity"`
 131  	Label        string            `json:"label"`
 132  	Plan         string            `json:"plan"`
 133  	Tag          string            `json:"tag"`
 134  	MinNodes     int               `json:"min_nodes,omitempty"`
 135  	MaxNodes     int               `json:"max_nodes,omitempty"`
 136  	AutoScaler   *bool             `json:"auto_scaler"`
 137  	Labels       map[string]string `json:"labels,omitempty"`
 138  	Taints       []Taint           `json:"taints,omitempty"`
 139  	UserData     string            `json:"user_data"`
 140  }
 141  
 142  // NodePoolReqUpdate struct used to update a node pool
 143  type NodePoolReqUpdate struct {
 144  	NodeQuantity int     `json:"node_quantity,omitempty"`
 145  	Tag          *string `json:"tag,omitempty"`
 146  	MinNodes     int     `json:"min_nodes,omitempty"`
 147  	MaxNodes     int     `json:"max_nodes,omitempty"`
 148  	AutoScaler   *bool   `json:"auto_scaler,omitempty"`
 149  
 150  	// Deprecated: Use CreateNodePoolLabel, DeleteNodePoolLabel instead
 151  	Labels map[string]string `json:"labels,omitempty"`
 152  
 153  	// Deprecated: Use CreateNodePoolTaint, DeleteNodePoolTaint instead
 154  	Taints []Taint `json:"taints,omitempty"`
 155  
 156  	UserData *string `json:"user_data,omitempty"`
 157  }
 158  
 159  // NodePoolLabel struct used to define a NodePool Label
 160  type NodePoolLabel struct {
 161  	ID    string `json:"id"`
 162  	Key   string `json:"key"`
 163  	Value string `json:"value"`
 164  }
 165  
 166  // NodePoolLabelReq struct used to create a NodePool Label
 167  type NodePoolLabelReq struct {
 168  	Key   string `json:"key"`
 169  	Value string `json:"value"`
 170  }
 171  
 172  // NodePoolTaint struct used to define a NodePool taint
 173  type NodePoolTaint struct {
 174  	ID     string `json:"id"`
 175  	Key    string `json:"key"`
 176  	Value  string `json:"value"`
 177  	Effect string `json:"effect"`
 178  }
 179  
 180  // NodePoolTaintReq struct used to create a NodePool taint
 181  type NodePoolTaintReq struct {
 182  	Key    string `json:"key"`
 183  	Value  string `json:"value"`
 184  	Effect string `json:"effect"`
 185  }
 186  
 187  type vkeClustersBase struct {
 188  	VKEClusters []Cluster `json:"vke_clusters"`
 189  	Meta        *Meta     `json:"meta"`
 190  }
 191  
 192  type vkeClusterBase struct {
 193  	VKECluster *Cluster `json:"vke_cluster"`
 194  }
 195  
 196  type vkeNodePoolsBase struct {
 197  	NodePools []NodePool `json:"node_pools"`
 198  	Meta      *Meta      `json:"meta"`
 199  }
 200  
 201  type vkeNodePoolLabelsBase struct {
 202  	Labels []NodePoolLabel `json:"labels"`
 203  }
 204  
 205  type vkeNodePoolLabelBase struct {
 206  	Label *NodePoolLabel `json:"label"`
 207  }
 208  
 209  type vkeNodePoolTaintsBase struct {
 210  	Taints []NodePoolTaint `json:"taints"`
 211  }
 212  
 213  type vkeNodePoolTaintBase struct {
 214  	Taint *NodePoolTaint `json:"taint"`
 215  }
 216  
 217  type vkeWorkerNodesBase struct {
 218  	WorkerNodes []Node `json:"worker_nodes"`
 219  	Meta        *Meta  `json:"meta"`
 220  }
 221  
 222  type vkeNodePoolBase struct {
 223  	NodePool *NodePool `json:"node_pool"`
 224  }
 225  
 226  // Versions that are supported for VKE
 227  type Versions struct {
 228  	Versions []string `json:"versions"`
 229  }
 230  
 231  // AvailableUpgrades for a given VKE cluster
 232  type availableUpgrades struct {
 233  	AvailableUpgrades []string `json:"available_upgrades"`
 234  }
 235  
 236  // ClusterUpgradeReq struct for vke upgradse
 237  type ClusterUpgradeReq struct {
 238  	UpgradeVersion string `json:"upgrade_version,omitempty"`
 239  }
 240  
 241  // CreateCluster will create a Kubernetes cluster.
 242  func (k *KubernetesHandler) CreateCluster(ctx context.Context, createReq *ClusterReq) (*Cluster, *http.Response, error) {
 243  	req, err := k.client.NewRequest(ctx, http.MethodPost, vkePath, createReq)
 244  	if err != nil {
 245  		return nil, nil, err
 246  	}
 247  
 248  	var k8 = new(vkeClusterBase)
 249  	resp, err := k.client.DoWithContext(ctx, req, &k8)
 250  	if err != nil {
 251  		return nil, resp, err
 252  	}
 253  
 254  	return k8.VKECluster, resp, nil
 255  }
 256  
 257  // GetCluster will return a Kubernetes cluster.
 258  func (k *KubernetesHandler) GetCluster(ctx context.Context, id string) (*Cluster, *http.Response, error) {
 259  	req, err := k.client.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s/%s", vkePath, id), nil)
 260  	if err != nil {
 261  		return nil, nil, err
 262  	}
 263  
 264  	k8 := new(vkeClusterBase)
 265  	resp, err := k.client.DoWithContext(ctx, req, &k8)
 266  	if err != nil {
 267  		return nil, resp, err
 268  	}
 269  
 270  	return k8.VKECluster, resp, nil
 271  }
 272  
 273  // ListClusters will return all kubernetes clusters.
 274  func (k *KubernetesHandler) ListClusters(ctx context.Context, options *ListOptions) ([]Cluster, *Meta, *http.Response, error) { //nolint:dupl,lll
 275  	req, err := k.client.NewRequest(ctx, http.MethodGet, vkePath, nil)
 276  	if err != nil {
 277  		return nil, nil, nil, err
 278  	}
 279  
 280  	newValues, err := query.Values(options)
 281  	if err != nil {
 282  		return nil, nil, nil, err
 283  	}
 284  
 285  	req.URL.RawQuery = newValues.Encode()
 286  
 287  	k8s := new(vkeClustersBase)
 288  	resp, err := k.client.DoWithContext(ctx, req, &k8s)
 289  	if err != nil {
 290  		return nil, nil, resp, err
 291  	}
 292  
 293  	return k8s.VKEClusters, k8s.Meta, resp, nil
 294  }
 295  
 296  // UpdateCluster updates label on VKE cluster
 297  func (k *KubernetesHandler) UpdateCluster(ctx context.Context, vkeID string, updateReq *ClusterReqUpdate) error {
 298  	req, err := k.client.NewRequest(ctx, http.MethodPut, fmt.Sprintf("%s/%s", vkePath, vkeID), updateReq)
 299  	if err != nil {
 300  		return err
 301  	}
 302  
 303  	_, err = k.client.DoWithContext(ctx, req, nil)
 304  	return err
 305  }
 306  
 307  // DeleteCluster will delete a Kubernetes cluster.
 308  func (k *KubernetesHandler) DeleteCluster(ctx context.Context, id string) error {
 309  	req, err := k.client.NewRequest(ctx, http.MethodDelete, fmt.Sprintf("%s/%s", vkePath, id), nil)
 310  	if err != nil {
 311  		return err
 312  	}
 313  	_, err = k.client.DoWithContext(ctx, req, nil)
 314  	return err
 315  }
 316  
 317  // DeleteClusterWithResources will delete a Kubernetes cluster and all related resources.
 318  func (k *KubernetesHandler) DeleteClusterWithResources(ctx context.Context, id string) error {
 319  	req, err := k.client.NewRequest(ctx, http.MethodDelete, fmt.Sprintf("%s/%s/delete-with-linked-resources", vkePath, id), nil)
 320  	if err != nil {
 321  		return err
 322  	}
 323  	_, err = k.client.DoWithContext(ctx, req, nil)
 324  	return err
 325  }
 326  
 327  // CreateNodePool creates a nodepool on a VKE cluster
 328  func (k *KubernetesHandler) CreateNodePool(ctx context.Context, vkeID string, nodePoolReq *NodePoolReq) (*NodePool, *http.Response, error) {
 329  	req, err := k.client.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/%s/node-pools", vkePath, vkeID), nodePoolReq)
 330  	if err != nil {
 331  		return nil, nil, err
 332  	}
 333  
 334  	n := new(vkeNodePoolBase)
 335  	resp, err := k.client.DoWithContext(ctx, req, n)
 336  	if err != nil {
 337  		return nil, resp, err
 338  	}
 339  
 340  	return n.NodePool, resp, nil
 341  }
 342  
 343  // ListNodePools will return all nodepools for a given VKE cluster
 344  func (k *KubernetesHandler) ListNodePools(ctx context.Context, vkeID string, options *ListOptions) ([]NodePool, *Meta, *http.Response, error) { //nolint:lll,dupl
 345  	req, err := k.client.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s/%s/node-pools", vkePath, vkeID), nil)
 346  	if err != nil {
 347  		return nil, nil, nil, err
 348  	}
 349  
 350  	newValues, err := query.Values(options)
 351  	if err != nil {
 352  		return nil, nil, nil, err
 353  	}
 354  
 355  	req.URL.RawQuery = newValues.Encode()
 356  
 357  	n := new(vkeNodePoolsBase)
 358  	resp, err := k.client.DoWithContext(ctx, req, &n)
 359  	if err != nil {
 360  		return nil, nil, resp, err
 361  	}
 362  
 363  	return n.NodePools, n.Meta, resp, nil
 364  }
 365  
 366  // GetNodePool will return a single nodepool
 367  func (k *KubernetesHandler) GetNodePool(ctx context.Context, vkeID, nodePoolID string) (*NodePool, *http.Response, error) {
 368  	req, err := k.client.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s/%s/node-pools/%s", vkePath, vkeID, nodePoolID), nil)
 369  	if err != nil {
 370  		return nil, nil, err
 371  	}
 372  
 373  	n := new(vkeNodePoolBase)
 374  	resp, err := k.client.DoWithContext(ctx, req, &n)
 375  	if err != nil {
 376  		return nil, resp, err
 377  	}
 378  
 379  	return n.NodePool, resp, nil
 380  }
 381  
 382  // UpdateNodePool will allow you change the quantity of nodes within a nodepool
 383  func (k *KubernetesHandler) UpdateNodePool(ctx context.Context, vkeID, nodePoolID string, updateReq *NodePoolReqUpdate) (*NodePool, *http.Response, error) { //nolint:lll
 384  	req, err := k.client.NewRequest(ctx, http.MethodPatch, fmt.Sprintf("%s/%s/node-pools/%s", vkePath, vkeID, nodePoolID), updateReq)
 385  	if err != nil {
 386  		return nil, nil, err
 387  	}
 388  
 389  	np := new(vkeNodePoolBase)
 390  	resp, err := k.client.DoWithContext(ctx, req, np)
 391  	if err != nil {
 392  		return nil, resp, err
 393  	}
 394  
 395  	return np.NodePool, resp, nil
 396  }
 397  
 398  // DeleteNodePool will remove a nodepool from a VKE cluster
 399  func (k *KubernetesHandler) DeleteNodePool(ctx context.Context, vkeID, nodePoolID string) error {
 400  	req, err := k.client.NewRequest(ctx, http.MethodDelete, fmt.Sprintf("%s/%s/node-pools/%s", vkePath, vkeID, nodePoolID), nil)
 401  	if err != nil {
 402  		return err
 403  	}
 404  
 405  	_, err = k.client.DoWithContext(ctx, req, nil)
 406  	return err
 407  }
 408  
 409  // ListNodePoolLabels retrieves a list of labels from a node pool
 410  func (k *KubernetesHandler) ListNodePoolLabels(ctx context.Context, vkeID, nodePoolID string) ([]NodePoolLabel, *http.Response, error) {
 411  	uri := fmt.Sprintf("%s/%s/node-pools/%s/labels", vkePath, vkeID, nodePoolID)
 412  
 413  	req, err := k.client.NewRequest(ctx, http.MethodGet, uri, nil)
 414  	if err != nil {
 415  		return nil, nil, err
 416  	}
 417  
 418  	labels := new(vkeNodePoolLabelsBase)
 419  	resp, err := k.client.DoWithContext(ctx, req, labels)
 420  	if err != nil {
 421  		return nil, resp, err
 422  	}
 423  
 424  	return labels.Labels, resp, nil
 425  }
 426  
 427  // CreateNodePoolLabel creates a label on a node pool
 428  func (k *KubernetesHandler) CreateNodePoolLabel(ctx context.Context, vkeID, nodePoolID string, nodePoolLabelReq *NodePoolLabelReq) (*NodePoolLabel, *http.Response, error) { //nolint:lll,dupl
 429  	uri := fmt.Sprintf("%s/%s/node-pools/%s/labels", vkePath, vkeID, nodePoolID)
 430  
 431  	req, err := k.client.NewRequest(ctx, http.MethodPost, uri, nodePoolLabelReq)
 432  	if err != nil {
 433  		return nil, nil, err
 434  	}
 435  
 436  	label := new(vkeNodePoolLabelBase)
 437  	resp, err := k.client.DoWithContext(ctx, req, label)
 438  	if err != nil {
 439  		return nil, resp, err
 440  	}
 441  
 442  	return label.Label, resp, nil
 443  }
 444  
 445  // GetNodePoolLabel retrieves a label from a node pool
 446  func (k *KubernetesHandler) GetNodePoolLabel(ctx context.Context, vkeID, nodePoolID, nodePoolLabelID string) (*NodePoolLabel, *http.Response, error) { //nolint:lll
 447  	uri := fmt.Sprintf("%s/%s/node-pools/%s/labels/%s", vkePath, vkeID, nodePoolID, nodePoolLabelID)
 448  
 449  	req, err := k.client.NewRequest(ctx, http.MethodGet, uri, nil)
 450  	if err != nil {
 451  		return nil, nil, err
 452  	}
 453  
 454  	label := new(vkeNodePoolLabelBase)
 455  	resp, err := k.client.DoWithContext(ctx, req, label)
 456  	if err != nil {
 457  		return nil, resp, err
 458  	}
 459  
 460  	return label.Label, resp, nil
 461  }
 462  
 463  // DeleteNodePoolLabel deletes a label from a node pool
 464  func (k *KubernetesHandler) DeleteNodePoolLabel(ctx context.Context, vkeID, nodePoolID, nodePoolLabelID string) error {
 465  	uri := fmt.Sprintf("%s/%s/node-pools/%s/labels/%s", vkePath, vkeID, nodePoolID, nodePoolLabelID)
 466  
 467  	req, err := k.client.NewRequest(ctx, http.MethodDelete, uri, nil)
 468  	if err != nil {
 469  		return err
 470  	}
 471  
 472  	_, err = k.client.DoWithContext(ctx, req, nil)
 473  	return err
 474  }
 475  
 476  // ListNodePoolTaints retrieves a list of taints from a node pool
 477  func (k *KubernetesHandler) ListNodePoolTaints(ctx context.Context, vkeID, nodePoolID string) ([]NodePoolTaint, *http.Response, error) {
 478  	uri := fmt.Sprintf("%s/%s/node-pools/%s/taints", vkePath, vkeID, nodePoolID)
 479  
 480  	req, err := k.client.NewRequest(ctx, http.MethodGet, uri, nil)
 481  	if err != nil {
 482  		return nil, nil, err
 483  	}
 484  
 485  	taints := new(vkeNodePoolTaintsBase)
 486  	resp, err := k.client.DoWithContext(ctx, req, taints)
 487  	if err != nil {
 488  		return nil, resp, err
 489  	}
 490  
 491  	return taints.Taints, resp, nil
 492  }
 493  
 494  // CreateNodePoolTaint creates a taint on a node pool
 495  func (k *KubernetesHandler) CreateNodePoolTaint(ctx context.Context, vkeID, nodePoolID string, nodePoolTaintReq *NodePoolTaintReq) (*NodePoolTaint, *http.Response, error) { //nolint:lll,dupl
 496  	uri := fmt.Sprintf("%s/%s/node-pools/%s/taints", vkePath, vkeID, nodePoolID)
 497  
 498  	req, err := k.client.NewRequest(ctx, http.MethodPost, uri, nodePoolTaintReq)
 499  	if err != nil {
 500  		return nil, nil, err
 501  	}
 502  
 503  	taint := new(vkeNodePoolTaintBase)
 504  	resp, err := k.client.DoWithContext(ctx, req, taint)
 505  	if err != nil {
 506  		return nil, resp, err
 507  	}
 508  
 509  	return taint.Taint, resp, nil
 510  }
 511  
 512  // GetNodePoolTaint retrieves a taint from a node pool
 513  func (k *KubernetesHandler) GetNodePoolTaint(ctx context.Context, vkeID, nodePoolID, nodePoolTaintID string) (*NodePoolTaint, *http.Response, error) { //nolint:lll
 514  	uri := fmt.Sprintf("%s/%s/node-pools/%s/taints/%s", vkePath, vkeID, nodePoolID, nodePoolTaintID)
 515  	req, err := k.client.NewRequest(ctx, http.MethodGet, uri, nil)
 516  	if err != nil {
 517  		return nil, nil, err
 518  	}
 519  
 520  	taint := new(vkeNodePoolTaintBase)
 521  	resp, err := k.client.DoWithContext(ctx, req, taint)
 522  	if err != nil {
 523  		return nil, resp, err
 524  	}
 525  
 526  	return taint.Taint, resp, nil
 527  }
 528  
 529  // DeleteNodePoolTaint deletes a taint on a node pool
 530  func (k *KubernetesHandler) DeleteNodePoolTaint(ctx context.Context, vkeID, nodePoolID, nodePoolTaintID string) error {
 531  	uri := fmt.Sprintf("%s/%s/node-pools/%s/taints/%s", vkePath, vkeID, nodePoolID, nodePoolTaintID)
 532  
 533  	req, err := k.client.NewRequest(ctx, http.MethodDelete, uri, nil)
 534  	if err != nil {
 535  		return err
 536  	}
 537  
 538  	_, err = k.client.DoWithContext(ctx, req, nil)
 539  	return err
 540  }
 541  
 542  // ListWorkerNodes retrieves a list of all worker nodes for a given node pool
 543  func (k *KubernetesHandler) ListWorkerNodes(ctx context.Context, vkeID, nodePoolID string, options *ListOptions) ([]Node, *Meta, *http.Response, error) { //nolint:lll,dupl
 544  	uri := fmt.Sprintf("%s/%s/node-pools/%s/nodes", vkePath, vkeID, nodePoolID)
 545  
 546  	req, err := k.client.NewRequest(ctx, http.MethodGet, uri, nil)
 547  	if err != nil {
 548  		return nil, nil, nil, err
 549  	}
 550  
 551  	newValues, err := query.Values(options)
 552  	if err != nil {
 553  		return nil, nil, nil, err
 554  	}
 555  
 556  	req.URL.RawQuery = newValues.Encode()
 557  
 558  	nodes := new(vkeWorkerNodesBase)
 559  	resp, err := k.client.DoWithContext(ctx, req, &nodes)
 560  	if err != nil {
 561  		return nil, nil, resp, err
 562  	}
 563  
 564  	return nodes.WorkerNodes, nodes.Meta, resp, nil
 565  }
 566  
 567  // DeleteNodePoolInstance will remove a specified node from a nodepool
 568  func (k *KubernetesHandler) DeleteNodePoolInstance(ctx context.Context, vkeID, nodePoolID, nodeID string) error {
 569  	req, err := k.client.NewRequest(
 570  		ctx,
 571  		http.MethodDelete,
 572  		fmt.Sprintf("%s/%s/node-pools/%s/nodes/%s", vkePath, vkeID, nodePoolID, nodeID),
 573  		nil,
 574  	)
 575  	if err != nil {
 576  		return err
 577  	}
 578  
 579  	_, err = k.client.DoWithContext(ctx, req, nil)
 580  	return err
 581  }
 582  
 583  // RecycleNodePoolInstance will recycle (destroy + redeploy) a given node on a nodepool
 584  func (k *KubernetesHandler) RecycleNodePoolInstance(ctx context.Context, vkeID, nodePoolID, nodeID string) error {
 585  	req, err := k.client.NewRequest(
 586  		ctx,
 587  		http.MethodPost,
 588  		fmt.Sprintf("%s/%s/node-pools/%s/nodes/%s/recycle", vkePath, vkeID, nodePoolID, nodeID),
 589  		nil,
 590  	)
 591  	if err != nil {
 592  		return err
 593  	}
 594  
 595  	_, err = k.client.DoWithContext(ctx, req, nil)
 596  	return err
 597  }
 598  
 599  // GetKubeConfig returns the kubeconfig for the specified VKE cluster
 600  func (k *KubernetesHandler) GetKubeConfig(ctx context.Context, vkeID string) (*KubeConfig, *http.Response, error) {
 601  	req, err := k.client.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s/%s/config", vkePath, vkeID), nil)
 602  	if err != nil {
 603  		return nil, nil, err
 604  	}
 605  
 606  	kc := new(KubeConfig)
 607  	resp, err := k.client.DoWithContext(ctx, req, &kc)
 608  	if err != nil {
 609  		return nil, resp, err
 610  	}
 611  
 612  	return kc, resp, nil
 613  }
 614  
 615  // GetVersions returns the supported kubernetes versions
 616  func (k *KubernetesHandler) GetVersions(ctx context.Context) (*Versions, *http.Response, error) {
 617  	uri := "/v2/kubernetes/versions"
 618  	req, err := k.client.NewRequest(ctx, http.MethodGet, uri, nil)
 619  	if err != nil {
 620  		return nil, nil, err
 621  	}
 622  
 623  	versions := new(Versions)
 624  	resp, err := k.client.DoWithContext(ctx, req, &versions)
 625  	if err != nil {
 626  		return nil, resp, err
 627  	}
 628  
 629  	return versions, resp, nil
 630  }
 631  
 632  // GetUpgrades returns all version a VKE cluster can upgrade to
 633  func (k *KubernetesHandler) GetUpgrades(ctx context.Context, vkeID string) ([]string, *http.Response, error) {
 634  	req, err := k.client.NewRequest(ctx, http.MethodGet, fmt.Sprintf("%s/%s/available-upgrades", vkePath, vkeID), nil)
 635  	if err != nil {
 636  		return nil, nil, err
 637  	}
 638  
 639  	upgrades := new(availableUpgrades)
 640  	resp, err := k.client.DoWithContext(ctx, req, &upgrades)
 641  	if err != nil {
 642  		return nil, resp, err
 643  	}
 644  
 645  	return upgrades.AvailableUpgrades, resp, nil
 646  }
 647  
 648  // Upgrade beings a VKE cluster upgrade
 649  func (k *KubernetesHandler) Upgrade(ctx context.Context, vkeID string, body *ClusterUpgradeReq) error {
 650  	req, err := k.client.NewRequest(ctx, http.MethodPost, fmt.Sprintf("%s/%s/upgrades", vkePath, vkeID), body)
 651  	if err != nil {
 652  		return err
 653  	}
 654  
 655  	_, err = k.client.DoWithContext(ctx, req, nil)
 656  	return err
 657  }
 658