diff --git a/api/k8s/v1/k8s_helpers.go b/api/k8s/v1/k8s_helpers.go new file mode 100644 index 000000000..9e8f71b6e --- /dev/null +++ b/api/k8s/v1/k8s_helpers.go @@ -0,0 +1,145 @@ +package k8s + +import ( + "time" + + "github.com/scaleway/scaleway-sdk-go/internal/async" + "github.com/scaleway/scaleway-sdk-go/internal/errors" + "github.com/scaleway/scaleway-sdk-go/scw" +) + +var ( + // RetryInterval is needed when running recorded tests (e.g. on scaleway-cli) + // it allows to execute the WaitFor funcs immediately + RetryInterval = defaultRetryInterval +) + +const ( + waitForClusterDefaultTimeout = time.Minute * 15 + waitForPoolDefaultTimeout = time.Minute * 15 + waitForNodeDefaultTimeout = time.Minute * 15 + defaultRetryInterval = time.Second * 5 +) + +// WaitForClusterRequest is used by WaitForCluster method. +type WaitForClusterRequest struct { + ClusterID string + Region scw.Region + Status ClusterStatus + Timeout *time.Duration +} + +// WaitForCluster waits for the cluster to be in a "terminal state" before returning. +func (s *API) WaitForCluster(req *WaitForClusterRequest) (*Cluster, error) { + timeout := waitForClusterDefaultTimeout + if req.Timeout != nil { + timeout = *req.Timeout + } + terminalStatus := map[ClusterStatus]struct{}{ + ClusterStatusReady: {}, + ClusterStatusLocked: {}, + ClusterStatusDeleted: {}, + ClusterStatusPoolRequired: {}, + } + + cluster, err := async.WaitSync(&async.WaitSyncConfig{ + Get: func() (interface{}, bool, error) { + cluster, err := s.GetCluster(&GetClusterRequest{ + ClusterID: req.ClusterID, + Region: req.Region, + }) + if err != nil { + return nil, false, err + } + + _, isTerminal := terminalStatus[cluster.Status] + return cluster, isTerminal, nil + }, + Timeout: timeout, + IntervalStrategy: async.LinearIntervalStrategy(RetryInterval), + }) + if err != nil { + return nil, errors.Wrap(err, "waiting for cluster failed") + } + return cluster.(*Cluster), nil +} + +// WaitForPoolRequest is used by WaitForPool method. +type WaitForPoolRequest struct { + PoolID string + Region scw.Region + Timeout *time.Duration +} + +// WaitForPool waits for a pool to be ready +func (s *API) WaitForPool(req *WaitForPoolRequest) (*Pool, error) { + terminalStatus := map[PoolStatus]struct{}{ + PoolStatusReady: {}, + PoolStatusWarning: {}, + } + + timeout := waitForPoolDefaultTimeout + if req.Timeout != nil { + timeout = *req.Timeout + } + + pool, err := async.WaitSync(&async.WaitSyncConfig{ + Get: func() (interface{}, bool, error) { + res, err := s.GetPool(&GetPoolRequest{ + PoolID: req.PoolID, + Region: req.Region, + }) + + if err != nil { + return nil, false, err + } + _, isTerminal := terminalStatus[res.Status] + + return res, isTerminal, nil + }, + Timeout: timeout, + IntervalStrategy: async.LinearIntervalStrategy(RetryInterval), + }) + + return pool.(*Pool), err +} + +// WaitForNodeRequest is used by WaitForNode method. +type WaitForNodeRequest struct { + NodeID string + Region scw.Region + Timeout *time.Duration +} + +// WaitForNode waits for a Node to be ready +func (s *API) WaitForNode(req *WaitForNodeRequest) (*Node, error) { + terminalStatus := map[NodeStatus]struct{}{ + NodeStatusCreationError: {}, + NodeStatusReady: {}, + } + + timeout := waitForNodeDefaultTimeout + if req.Timeout != nil { + timeout = *req.Timeout + } + + node, err := async.WaitSync(&async.WaitSyncConfig{ + Get: func() (interface{}, bool, error) { + res, err := s.GetNode(&GetNodeRequest{ + NodeID: req.NodeID, + Region: req.Region, + }) + + if err != nil { + return nil, false, err + } + _, isTerminal := terminalStatus[res.Status] + + return res, isTerminal, nil + }, + Timeout: timeout, + IntervalStrategy: async.LinearIntervalStrategy(RetryInterval), + }) + + return node.(*Node), err +} diff --git a/api/k8s/v1/kubeconfig.go b/api/k8s/v1/kubeconfig.go new file mode 100644 index 000000000..70260675f --- /dev/null +++ b/api/k8s/v1/kubeconfig.go @@ -0,0 +1,118 @@ +package k8s + +import ( + "io/ioutil" + + "github.com/scaleway/scaleway-sdk-go/internal/errors" + "github.com/scaleway/scaleway-sdk-go/scw" + "gopkg.in/yaml.v2" +) + +// Kubeconfig represents a kubernetes kubeconfig file +type Kubeconfig struct { + raw []byte + APIVersion string `yaml:"apiVersion"` + Kind string `yaml:"kind"` + CurrentContext string `yaml:"current-context"` + Clusters []*KubeconfigClusterWithName `yaml:"clusters"` + Contexts []*KubeconfigContextWithName `yaml:"contexts"` + Users []*KubeconfigUserWithName `yaml:"users"` +} + +// KubeconfigUserWithName represents a named cluster in the kubeconfig file +type KubeconfigClusterWithName struct { + Name string `yaml:"name"` + Cluster KubeconfigCluster `yaml:"cluster"` +} + +// KubeconfigCluster represents a cluster in the kubeconfig file +type KubeconfigCluster struct { + Server string `yaml:"server,omitempty"` + CertificateAuthorityData string `yaml:"certificate-authority-data,omitempty"` +} + +// KubeconfigContextWithName represents a named context in the kubeconfig file +type KubeconfigContextWithName struct { + Name string `yaml:"name"` + Context KubeconfigContext `yaml:"context"` +} + +// KubeconfigContext represents a context in the kubeconfig file +type KubeconfigContext struct { + Cluster string `yaml:"cluster"` + Namespace string `yaml:"namespace,omitempty"` + User string `yaml:"user"` +} + +// KubeconfigUserWithName represents a named user in the kubeconfig file +type KubeconfigUserWithName struct { + Name string `yaml:"name"` + User KubeconfigUser `yaml:"user"` +} + +// KubeconfigUser represents a user in the kubeconfig file +type KubeconfigUser struct { + ClientCertificateData string `yaml:"client-certificate-data,omitempty"` + ClientKeyData string `yaml:"client-key-data,omitempty"` + Password string `yaml:"password,omitempty"` + Username string `yaml:"username,omitempty"` + Token string `yaml:"token,omitempty"` +} + +// GetRaw returns the raw bytes of the kubeconfig +func (k *Kubeconfig) GetRaw() []byte { + return k.raw +} + +// GetServer returns the server URL of the cluster in the kubeconfig +func (k *Kubeconfig) GetServer() (string, error) { + if len(k.Clusters) != 1 { + return "", errors.New("kubeconfig should have only one cluster") + } + + return k.Clusters[0].Cluster.Server, nil +} + +// GetCertificateAuthorityData returns the server certificate authority data of the cluster in the kubeconfig +func (k *Kubeconfig) GetCertificateAuthorityData() (string, error) { + if len(k.Clusters) != 1 { + return "", errors.New("kubeconfig should have only one cluster") + } + + return k.Clusters[0].Cluster.CertificateAuthorityData, nil +} + +// GetToken returns the token for the cluster in the kubeconfig +func (k *Kubeconfig) GetToken() (string, error) { + if len(k.Users) != 1 { + return "", errors.New("kubeconfig should have only one user") + } + + return k.Users[0].User.Token, nil +} + +// GetClusterKubeConfig downloads the kubeconfig for the given cluster +func (s *API) GetClusterKubeConfig(req *GetClusterKubeConfigRequest, opts ...scw.RequestOption) (*Kubeconfig, error) { + kubeconfigFile, err := s.getClusterKubeConfig(&GetClusterKubeConfigRequest{ + Region: req.Region, + ClusterID: req.ClusterID, + }) + if err != nil { + return nil, errors.Wrap(err, "error getting cluster kubeconfig") + } + + kubeconfigContent, err := ioutil.ReadAll(kubeconfigFile.Content) + if err != nil { + return nil, errors.Wrap(err, "error reading kubeconfig content") + } + + var kubeconfig Kubeconfig + err = yaml.Unmarshal(kubeconfigContent, &kubeconfig) + if err != nil { + return nil, errors.Wrap(err, "error unmarshaling kubeconfig") + } + + kubeconfig.raw = kubeconfigContent + + return &kubeconfig, nil +}