435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
433 lines
15 KiB
Go
433 lines
15 KiB
Go
package godo
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
kubernetesBasePath = "/v2/kubernetes"
|
|
kubernetesClustersPath = kubernetesBasePath + "/clusters"
|
|
kubernetesOptionsPath = kubernetesBasePath + "/options"
|
|
)
|
|
|
|
// KubernetesService is an interface for interfacing with the kubernetes endpoints
|
|
// of the DigitalOcean API.
|
|
// See: https://developers.digitalocean.com/documentation/v2#kubernetes
|
|
type KubernetesService interface {
|
|
Create(context.Context, *KubernetesClusterCreateRequest) (*KubernetesCluster, *Response, error)
|
|
Get(context.Context, string) (*KubernetesCluster, *Response, error)
|
|
GetKubeConfig(context.Context, string) (*KubernetesClusterConfig, *Response, error)
|
|
List(context.Context, *ListOptions) ([]*KubernetesCluster, *Response, error)
|
|
Update(context.Context, string, *KubernetesClusterUpdateRequest) (*KubernetesCluster, *Response, error)
|
|
Delete(context.Context, string) (*Response, error)
|
|
|
|
CreateNodePool(ctx context.Context, clusterID string, req *KubernetesNodePoolCreateRequest) (*KubernetesNodePool, *Response, error)
|
|
GetNodePool(ctx context.Context, clusterID, poolID string) (*KubernetesNodePool, *Response, error)
|
|
ListNodePools(ctx context.Context, clusterID string, opts *ListOptions) ([]*KubernetesNodePool, *Response, error)
|
|
UpdateNodePool(ctx context.Context, clusterID, poolID string, req *KubernetesNodePoolUpdateRequest) (*KubernetesNodePool, *Response, error)
|
|
RecycleNodePoolNodes(ctx context.Context, clusterID, poolID string, req *KubernetesNodePoolRecycleNodesRequest) (*Response, error)
|
|
DeleteNodePool(ctx context.Context, clusterID, poolID string) (*Response, error)
|
|
|
|
GetOptions(context.Context) (*KubernetesOptions, *Response, error)
|
|
}
|
|
|
|
var _ KubernetesService = &KubernetesServiceOp{}
|
|
|
|
// KubernetesServiceOp handles communication with Kubernetes methods of the DigitalOcean API.
|
|
type KubernetesServiceOp struct {
|
|
client *Client
|
|
}
|
|
|
|
// KubernetesClusterCreateRequest represents a request to create a Kubernetes cluster.
|
|
type KubernetesClusterCreateRequest struct {
|
|
Name string `json:"name,omitempty"`
|
|
RegionSlug string `json:"region,omitempty"`
|
|
VersionSlug string `json:"version,omitempty"`
|
|
Tags []string `json:"tags,omitempty"`
|
|
|
|
NodePools []*KubernetesNodePoolCreateRequest `json:"node_pools,omitempty"`
|
|
}
|
|
|
|
// KubernetesClusterUpdateRequest represents a request to update a Kubernetes cluster.
|
|
type KubernetesClusterUpdateRequest struct {
|
|
Name string `json:"name,omitempty"`
|
|
Tags []string `json:"tags,omitempty"`
|
|
}
|
|
|
|
// KubernetesNodePoolCreateRequest represents a request to create a node pool for a
|
|
// Kubernetes cluster.
|
|
type KubernetesNodePoolCreateRequest struct {
|
|
Name string `json:"name,omitempty"`
|
|
Size string `json:"size,omitempty"`
|
|
Count int `json:"count,omitempty"`
|
|
Tags []string `json:"tags,omitempty"`
|
|
}
|
|
|
|
// KubernetesNodePoolUpdateRequest represents a request to update a node pool in a
|
|
// Kubernetes cluster.
|
|
type KubernetesNodePoolUpdateRequest struct {
|
|
Name string `json:"name,omitempty"`
|
|
Count int `json:"count,omitempty"`
|
|
Tags []string `json:"tags,omitempty"`
|
|
}
|
|
|
|
// KubernetesNodePoolRecycleNodesRequest represents a request to recycle a set of
|
|
// nodes in a node pool. This will recycle the nodes by ID.
|
|
type KubernetesNodePoolRecycleNodesRequest struct {
|
|
Nodes []string `json:"nodes,omitempty"`
|
|
}
|
|
|
|
// KubernetesCluster represents a Kubernetes cluster.
|
|
type KubernetesCluster struct {
|
|
ID string `json:"id,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
RegionSlug string `json:"region,omitempty"`
|
|
VersionSlug string `json:"version,omitempty"`
|
|
ClusterSubnet string `json:"cluster_subnet,omitempty"`
|
|
ServiceSubnet string `json:"service_subnet,omitempty"`
|
|
IPv4 string `json:"ipv4,omitempty"`
|
|
Endpoint string `json:"endpoint,omitempty"`
|
|
Tags []string `json:"tags,omitempty"`
|
|
|
|
NodePools []*KubernetesNodePool `json:"node_pools,omitempty"`
|
|
|
|
Status *KubernetesClusterStatus `json:"status,omitempty"`
|
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
|
}
|
|
|
|
// Possible states for a cluster.
|
|
const (
|
|
KubernetesClusterStatusProvisioning = KubernetesClusterStatusState("provisioning")
|
|
KubernetesClusterStatusRunning = KubernetesClusterStatusState("running")
|
|
KubernetesClusterStatusDegraded = KubernetesClusterStatusState("degraded")
|
|
KubernetesClusterStatusError = KubernetesClusterStatusState("error")
|
|
KubernetesClusterStatusDeleted = KubernetesClusterStatusState("deleted")
|
|
KubernetesClusterStatusInvalid = KubernetesClusterStatusState("invalid")
|
|
)
|
|
|
|
// KubernetesClusterStatusState represents states for a cluster.
|
|
type KubernetesClusterStatusState string
|
|
|
|
var _ encoding.TextUnmarshaler = (*KubernetesClusterStatusState)(nil)
|
|
|
|
// UnmarshalText unmarshals the state.
|
|
func (s *KubernetesClusterStatusState) UnmarshalText(text []byte) error {
|
|
switch KubernetesClusterStatusState(strings.ToLower(string(text))) {
|
|
case KubernetesClusterStatusProvisioning:
|
|
*s = KubernetesClusterStatusProvisioning
|
|
case KubernetesClusterStatusRunning:
|
|
*s = KubernetesClusterStatusRunning
|
|
case KubernetesClusterStatusDegraded:
|
|
*s = KubernetesClusterStatusDegraded
|
|
case KubernetesClusterStatusError:
|
|
*s = KubernetesClusterStatusError
|
|
case KubernetesClusterStatusDeleted:
|
|
*s = KubernetesClusterStatusDeleted
|
|
case "", KubernetesClusterStatusInvalid:
|
|
*s = KubernetesClusterStatusInvalid
|
|
default:
|
|
return fmt.Errorf("unknown cluster state %q", string(text))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// KubernetesClusterStatus describes the status of a cluster.
|
|
type KubernetesClusterStatus struct {
|
|
State KubernetesClusterStatusState `json:"state,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
// KubernetesNodePool represents a node pool in a Kubernetes cluster.
|
|
type KubernetesNodePool struct {
|
|
ID string `json:"id,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Size string `json:"size,omitempty"`
|
|
Count int `json:"count,omitempty"`
|
|
Tags []string `json:"tags,omitempty"`
|
|
|
|
Nodes []*KubernetesNode `json:"nodes,omitempty"`
|
|
}
|
|
|
|
// KubernetesNode represents a Node in a node pool in a Kubernetes cluster.
|
|
type KubernetesNode struct {
|
|
ID string `json:"id,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
Status *KubernetesNodeStatus `json:"status,omitempty"`
|
|
|
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
|
}
|
|
|
|
// KubernetesNodeStatus represents the status of a particular Node in a Kubernetes cluster.
|
|
type KubernetesNodeStatus struct {
|
|
State string `json:"state,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
}
|
|
|
|
// KubernetesOptions represents options available for creating Kubernetes clusters.
|
|
type KubernetesOptions struct {
|
|
Versions []*KubernetesVersion `json:"versions,omitempty"`
|
|
Regions []*KubernetesRegion `json:"regions,omitempty"`
|
|
Sizes []*KubernetesNodeSize `json:"sizes,omitempty"`
|
|
}
|
|
|
|
// KubernetesVersion is a DigitalOcean Kubernetes release.
|
|
type KubernetesVersion struct {
|
|
Slug string `json:"slug,omitempty"`
|
|
KubernetesVersion string `json:"kubernetes_version,omitempty"`
|
|
}
|
|
|
|
// KubernetesNodeSize is a node sizes supported for Kubernetes clusters.
|
|
type KubernetesNodeSize struct {
|
|
Name string `json:"name"`
|
|
Slug string `json:"slug"`
|
|
}
|
|
|
|
// KubernetesRegion is a region usable by Kubernetes clusters.
|
|
type KubernetesRegion struct {
|
|
Name string `json:"name"`
|
|
Slug string `json:"slug"`
|
|
}
|
|
|
|
type kubernetesClustersRoot struct {
|
|
Clusters []*KubernetesCluster `json:"kubernetes_clusters,omitempty"`
|
|
Links *Links `json:"links,omitempty"`
|
|
}
|
|
|
|
type kubernetesClusterRoot struct {
|
|
Cluster *KubernetesCluster `json:"kubernetes_cluster,omitempty"`
|
|
}
|
|
|
|
type kubernetesNodePoolRoot struct {
|
|
NodePool *KubernetesNodePool `json:"node_pool,omitempty"`
|
|
}
|
|
|
|
type kubernetesNodePoolsRoot struct {
|
|
NodePools []*KubernetesNodePool `json:"node_pools,omitempty"`
|
|
Links *Links `json:"links,omitempty"`
|
|
}
|
|
|
|
// Get retrieves the details of a Kubernetes cluster.
|
|
func (svc *KubernetesServiceOp) Get(ctx context.Context, clusterID string) (*KubernetesCluster, *Response, error) {
|
|
path := fmt.Sprintf("%s/%s", kubernetesClustersPath, clusterID)
|
|
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
root := new(kubernetesClusterRoot)
|
|
resp, err := svc.client.Do(ctx, req, root)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
return root.Cluster, resp, nil
|
|
}
|
|
|
|
// Create creates a Kubernetes cluster.
|
|
func (svc *KubernetesServiceOp) Create(ctx context.Context, create *KubernetesClusterCreateRequest) (*KubernetesCluster, *Response, error) {
|
|
path := kubernetesClustersPath
|
|
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, create)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
root := new(kubernetesClusterRoot)
|
|
resp, err := svc.client.Do(ctx, req, root)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
return root.Cluster, resp, nil
|
|
}
|
|
|
|
// Delete deletes a Kubernetes cluster. There is no way to recover a cluster
|
|
// once it has been destroyed.
|
|
func (svc *KubernetesServiceOp) Delete(ctx context.Context, clusterID string) (*Response, error) {
|
|
path := fmt.Sprintf("%s/%s", kubernetesClustersPath, clusterID)
|
|
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := svc.client.Do(ctx, req, nil)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// List returns a list of the Kubernetes clusters visible with the caller's API token.
|
|
func (svc *KubernetesServiceOp) List(ctx context.Context, opts *ListOptions) ([]*KubernetesCluster, *Response, error) {
|
|
path := kubernetesClustersPath
|
|
path, err := addOptions(path, opts)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
root := new(kubernetesClustersRoot)
|
|
resp, err := svc.client.Do(ctx, req, root)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
return root.Clusters, resp, nil
|
|
}
|
|
|
|
// KubernetesClusterConfig is the content of a Kubernetes config file, which can be
|
|
// used to interact with your Kubernetes cluster using `kubectl`.
|
|
// See: https://kubernetes.io/docs/tasks/tools/install-kubectl/
|
|
type KubernetesClusterConfig struct {
|
|
KubeconfigYAML []byte
|
|
}
|
|
|
|
// GetKubeConfig returns a Kubernetes config file for the specified cluster.
|
|
func (svc *KubernetesServiceOp) GetKubeConfig(ctx context.Context, clusterID string) (*KubernetesClusterConfig, *Response, error) {
|
|
path := fmt.Sprintf("%s/%s/kubeconfig", kubernetesClustersPath, clusterID)
|
|
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
configBytes := bytes.NewBuffer(nil)
|
|
resp, err := svc.client.Do(ctx, req, configBytes)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
res := &KubernetesClusterConfig{
|
|
KubeconfigYAML: configBytes.Bytes(),
|
|
}
|
|
return res, resp, nil
|
|
}
|
|
|
|
// Update updates a Kubernetes cluster's properties.
|
|
func (svc *KubernetesServiceOp) Update(ctx context.Context, clusterID string, update *KubernetesClusterUpdateRequest) (*KubernetesCluster, *Response, error) {
|
|
path := fmt.Sprintf("%s/%s", kubernetesClustersPath, clusterID)
|
|
req, err := svc.client.NewRequest(ctx, http.MethodPut, path, update)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
root := new(kubernetesClusterRoot)
|
|
resp, err := svc.client.Do(ctx, req, root)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
return root.Cluster, resp, nil
|
|
}
|
|
|
|
// CreateNodePool creates a new node pool in an existing Kubernetes cluster.
|
|
func (svc *KubernetesServiceOp) CreateNodePool(ctx context.Context, clusterID string, create *KubernetesNodePoolCreateRequest) (*KubernetesNodePool, *Response, error) {
|
|
path := fmt.Sprintf("%s/%s/node_pools", kubernetesClustersPath, clusterID)
|
|
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, create)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
root := new(kubernetesNodePoolRoot)
|
|
resp, err := svc.client.Do(ctx, req, root)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
return root.NodePool, resp, nil
|
|
}
|
|
|
|
// GetNodePool retrieves an existing node pool in a Kubernetes cluster.
|
|
func (svc *KubernetesServiceOp) GetNodePool(ctx context.Context, clusterID, poolID string) (*KubernetesNodePool, *Response, error) {
|
|
path := fmt.Sprintf("%s/%s/node_pools/%s", kubernetesClustersPath, clusterID, poolID)
|
|
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
root := new(kubernetesNodePoolRoot)
|
|
resp, err := svc.client.Do(ctx, req, root)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
return root.NodePool, resp, nil
|
|
}
|
|
|
|
// ListNodePools lists all the node pools found in a Kubernetes cluster.
|
|
func (svc *KubernetesServiceOp) ListNodePools(ctx context.Context, clusterID string, opts *ListOptions) ([]*KubernetesNodePool, *Response, error) {
|
|
path := fmt.Sprintf("%s/%s/node_pools", kubernetesClustersPath, clusterID)
|
|
path, err := addOptions(path, opts)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
root := new(kubernetesNodePoolsRoot)
|
|
resp, err := svc.client.Do(ctx, req, root)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
return root.NodePools, resp, nil
|
|
}
|
|
|
|
// UpdateNodePool updates the details of an existing node pool.
|
|
func (svc *KubernetesServiceOp) UpdateNodePool(ctx context.Context, clusterID, poolID string, update *KubernetesNodePoolUpdateRequest) (*KubernetesNodePool, *Response, error) {
|
|
path := fmt.Sprintf("%s/%s/node_pools/%s", kubernetesClustersPath, clusterID, poolID)
|
|
req, err := svc.client.NewRequest(ctx, http.MethodPut, path, update)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
root := new(kubernetesNodePoolRoot)
|
|
resp, err := svc.client.Do(ctx, req, root)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
return root.NodePool, resp, nil
|
|
}
|
|
|
|
// RecycleNodePoolNodes schedules nodes in a node pool for recycling.
|
|
func (svc *KubernetesServiceOp) RecycleNodePoolNodes(ctx context.Context, clusterID, poolID string, recycle *KubernetesNodePoolRecycleNodesRequest) (*Response, error) {
|
|
path := fmt.Sprintf("%s/%s/node_pools/%s/recycle", kubernetesClustersPath, clusterID, poolID)
|
|
req, err := svc.client.NewRequest(ctx, http.MethodPost, path, recycle)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := svc.client.Do(ctx, req, nil)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// DeleteNodePool deletes a node pool, and subsequently all the nodes in that pool.
|
|
func (svc *KubernetesServiceOp) DeleteNodePool(ctx context.Context, clusterID, poolID string) (*Response, error) {
|
|
path := fmt.Sprintf("%s/%s/node_pools/%s", kubernetesClustersPath, clusterID, poolID)
|
|
req, err := svc.client.NewRequest(ctx, http.MethodDelete, path, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resp, err := svc.client.Do(ctx, req, nil)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
type kubernetesOptionsRoot struct {
|
|
Options *KubernetesOptions `json:"options,omitempty"`
|
|
Links *Links `json:"links,omitempty"`
|
|
}
|
|
|
|
// GetOptions returns options about the Kubernetes service, such as the versions available for
|
|
// cluster creation.
|
|
func (svc *KubernetesServiceOp) GetOptions(ctx context.Context) (*KubernetesOptions, *Response, error) {
|
|
path := kubernetesOptionsPath
|
|
req, err := svc.client.NewRequest(ctx, http.MethodGet, path, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
root := new(kubernetesOptionsRoot)
|
|
resp, err := svc.client.Do(ctx, req, root)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
return root.Options, resp, nil
|
|
}
|