diff --git a/pkg/api/v1/kubernetes/clusters/clusters.go b/pkg/api/v1/kubernetes/clusters/clusters.go deleted file mode 100644 index 2810d78b..00000000 --- a/pkg/api/v1/kubernetes/clusters/clusters.go +++ /dev/null @@ -1,367 +0,0 @@ -// package clusters is used to create and manage STACKIT Kubernetes Enging (SKE) clusters - -package clusters - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "strings" - - "github.com/SchwarzIT/community-stackit-go-client/internal/common" - "github.com/SchwarzIT/community-stackit-go-client/pkg/consts" - "github.com/SchwarzIT/community-stackit-go-client/pkg/validate" - "github.com/SchwarzIT/community-stackit-go-client/pkg/wait" -) - -// constants -const ( - apiPath = consts.API_PATH_SKE_CLUSTERS - apiPathCluster = consts.API_PATH_SKE_WITH_CLUSTER_ID -) - -// New returns a new handler for the service -func New(c common.Client) *KubernetesClusterService { - return &KubernetesClusterService{ - Client: c, - } -} - -// KubernetesClusterService is the service that handles -// CRUD functionality for SKE clusters -type KubernetesClusterService common.Service - -// ClusterList is the response for listing clusters -type ClusterList struct { - Items []Cluster `json:"items"` -} - -// Cluster is a struct representation of a cluster in STACKIT api -type Cluster struct { - Name string `json:"name"` // 11 lowercase letters, numbers, or hyphens - Kubernetes Kubernetes `json:"kubernetes"` - Nodepools []NodePool `json:"nodepools"` - Maintenance *Maintenance `json:"maintenance,omitempty"` - Hibernation *Hibernation `json:"hibernation,omitempty"` - Extensions *Extensions `json:"extensions,omitempty"` - Status *Status `json:"status,omitempty"` -} - -// Kubernetes contains the cluster's kubernetes config -type Kubernetes struct { - Version string `json:"version"` - AllowPrivilegedContainers bool `json:"allowPrivilegedContainers"` -} - -// NodePool is a struct representing a node pool in the cluster -type NodePool struct { - Name string `json:"name,omitempty"` - Machine Machine `json:"machine"` - Minimum int `json:"minimum"` - Maximum int `json:"maximum"` - MaxSurge int `json:"maxSurge"` - MaxUnavailable int `json:"maxUnavailable"` - Volume Volume `json:"volume"` - Labels map[string]string `json:"labels"` - Taints []Taint `json:"taints"` - CRI CRI `json:"cri"` - AvailabilityZones []string `json:"availabilityZones"` -} - -// Machine contains information of the machine in the node pool -type Machine struct { - Type string `json:"type"` - Image MachineImage `json:"image"` -} - -// MachineImage contains information of the machine's image -type MachineImage struct { - Name string `json:"name"` - Version string `json:"version"` -} - -// Volume is the node pool volume information -type Volume struct { - Type string `json:"type"` - Size int `json:"size"` -} - -// Taint is a taint of the node pool -type Taint struct { - Effect string `json:"effect"` - Key string `json:"key"` - Value string `json:"value"` -} - -// CRI is the container runtime interface of the node pool -type CRI struct { - Name string `json:"name"` -} - -// Maintenance is the node pool's maintenance window -type Maintenance struct { - AutoUpdate MaintenanceAutoUpdate `json:"autoUpdate"` - TimeWindow MaintenanceTimeWindow `json:"timeWindow"` -} - -// MaintenanceAutoUpdate is the auto update confguration -type MaintenanceAutoUpdate struct { - KubernetesVersion bool `json:"kubernetesVersion"` - MachineImageVersion bool `json:"machineImageVersion"` -} - -// MaintenanceTimeWindow is when the maintenance window should happen -type MaintenanceTimeWindow struct { - Start string `json:"start"` - End string `json:"end"` -} - -// Hibernation schedule -type Hibernation struct { - Schedules []HibernationScedule `json:"schedules"` -} - -// HibernationScedule is the schedule for hibernation -type HibernationScedule struct { - Start string `json:"start"` - End string `json:"end"` - Timezone string `json:"timezone"` -} - -// Extensions represent SKE extensions -type Extensions struct { - Argus *ArgusExtension `json:"argus,omitempty"` -} - -// ArgusExtension is Argus extension -type ArgusExtension struct { - Enabled bool `json:"enabled"` - ArgusInstanceID string `json:"argusInstanceId"` -} - -// Status is the cluster status -type Status struct { - Hibernated bool `json:"hibernated"` - Aggregated string `json:"aggregated"` - Error struct { - Code string `json:"code"` - Message string `json:"message"` - Details string `json:"details"` - } `json:"error,omitempty"` -} - -// List returns the clusters in the project -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#operation/SkeService_ListClusters -func (svc *KubernetesClusterService) List(ctx context.Context, projectID string) (res ClusterList, err error) { - req, err := svc.Client.Request(ctx, http.MethodGet, fmt.Sprintf(apiPath, projectID), nil) - if err != nil { - return - } - - _, err = svc.Client.LegacyDo(req, &res) - return -} - -// Get returns the a cluster by project ID and cluster name -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#operation/SkeService_ListClusters -func (svc *KubernetesClusterService) Get(ctx context.Context, projectID, clusterName string) (res Cluster, err error) { - req, err := svc.Client.Request(ctx, http.MethodGet, fmt.Sprintf(apiPathCluster, projectID, clusterName), nil) - if err != nil { - return - } - - _, err = svc.Client.LegacyDo(req, &res) - return -} - -// CreateOrUpdate creates or updates a SKE cluster -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#operation/SkeService_CreateOrUpdateCluster -// The function also returns a wait functionality in case there's no error -// trigger wait by running `.Wait()` which returns the cluster information (clusters.Cluster struct) -func (svc *KubernetesClusterService) CreateOrUpdate( - ctx context.Context, - projectID string, - clusterName string, - clusterConfig Kubernetes, - nodePools []NodePool, - maintenance *Maintenance, - hibernation *Hibernation, - extensions *Extensions, -) (res Cluster, w *wait.Handler, err error) { - - // validate - if err = ValidateCluster( - clusterName, - clusterConfig, - nodePools, - maintenance, - hibernation, - extensions, - ); err != nil { - return res, nil, validate.WrapError(err) - } - - // build request body - body, _ := svc.buildCreateRequest( - projectID, - clusterName, - clusterConfig, - nodePools, - maintenance, - hibernation, - extensions, - ) - - // prepare & run request - req, err := svc.Client.Request(ctx, http.MethodPut, fmt.Sprintf(apiPathCluster, projectID, clusterName), body) - if err != nil { - return res, nil, err - } - - _, err = svc.Client.LegacyDo(req, &res) - - // prepare wait functionality - w = wait.New(svc.waitForCreation(ctx, projectID, clusterName)) - - return res, w, err -} - -func (svc *KubernetesClusterService) waitForCreation(ctx context.Context, projectID, clusterName string) wait.WaitFn { - return func() (res interface{}, done bool, err error) { - s, err := svc.Get(ctx, projectID, clusterName) - if err != nil { - return nil, false, err - } - status := s.Status.Aggregated - if status == consts.SKE_CLUSTER_STATUS_HEALTHY || status == consts.SKE_CLUSTER_STATUS_HIBERNATED { - return s, true, nil - } - return s, false, nil - } -} - -func (svc *KubernetesClusterService) buildCreateRequest( - projectID string, - clusterName string, - clusterConfig Kubernetes, - nodePools []NodePool, - maintenance *Maintenance, - hibernation *Hibernation, - extensions *Extensions, -) ([]byte, error) { - return json.Marshal(Cluster{ - Name: clusterName, - Kubernetes: clusterConfig, - Nodepools: nodePools, - Maintenance: maintenance, - Hibernation: hibernation, - Extensions: extensions, - }) -} - -// Delete deletes a SKE cluster -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#operation/SkeService_DeleteCluster -// Wait for deletion to complete by running the returned wait functionality `wait.Wait()` -func (svc *KubernetesClusterService) Delete(ctx context.Context, projectID, clusterName string) (w *wait.Handler, err error) { - req, err := svc.Client.Request(ctx, http.MethodDelete, fmt.Sprintf(apiPathCluster, projectID, clusterName), nil) - if err != nil { - return - } - _, err = svc.Client.LegacyDo(req, nil) - - w = wait.New(svc.waitForDeletion(ctx, projectID, clusterName)) - return w, err -} - -func (svc *KubernetesClusterService) waitForDeletion(ctx context.Context, projectID, clusterName string) wait.WaitFn { - return func() (res interface{}, done bool, err error) { - if _, err = svc.Get(ctx, projectID, clusterName); err != nil { - if strings.Contains(err.Error(), http.StatusText(http.StatusNotFound)) { - return nil, true, nil - } - return nil, false, err - } - return nil, false, nil - } -} - -// Hibernate triggers cluster hibernation -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#operation/SkeService_TriggerClusterHibernation -func (svc *KubernetesClusterService) Hibernate(ctx context.Context, projectID, clusterName string) (res Cluster, err error) { - req, err := svc.Client.Request(ctx, http.MethodPost, fmt.Sprintf(apiPathCluster+"/hibernate", projectID, clusterName), nil) - if err != nil { - return - } - - _, err = svc.Client.LegacyDo(req, &res) - return -} - -// Maintenance triggers cluster maintenance -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#operation/SkeService_TriggerClusterMaintenance -func (svc *KubernetesClusterService) Maintenance(ctx context.Context, projectID, clusterName string) (res Cluster, err error) { - req, err := svc.Client.Request(ctx, http.MethodPost, fmt.Sprintf(apiPathCluster+"/maintenance", projectID, clusterName), nil) - if err != nil { - return - } - - _, err = svc.Client.LegacyDo(req, &res) - return -} - -// Reconcile triggers cluster reconciliation -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#operation/SkeService_TriggerClusterReconciliation -func (svc *KubernetesClusterService) Reconcile(ctx context.Context, projectID, clusterName string) (res Cluster, err error) { - req, err := svc.Client.Request(ctx, http.MethodPost, fmt.Sprintf(apiPathCluster+"/reconcile", projectID, clusterName), nil) - if err != nil { - return - } - - _, err = svc.Client.LegacyDo(req, &res) - return -} - -// Wakeup triggers cluster wakeup -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#operation/SkeService_TriggerClusterWakeup -func (svc *KubernetesClusterService) Wakeup(ctx context.Context, projectID, clusterName string) (res Cluster, err error) { - req, err := svc.Client.Request(ctx, http.MethodPost, fmt.Sprintf(apiPathCluster+"/wakeup", projectID, clusterName), nil) - if err != nil { - return - } - - _, err = svc.Client.LegacyDo(req, &res) - return -} - -// Credentials is the struct response for cluster credentils -type Credentials struct { - Server string `json:"server"` - Kubeconfig string `json:"kubeconfig"` - CertificateAuthorityData string `json:"certificateAuthorityData"` - Token string `json:"token"` -} - -// GetCredential returns the a credentials for the cluster -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#tag/Credentials -func (svc *KubernetesClusterService) GetCredential(ctx context.Context, projectID, clusterName string) (res Credentials, err error) { - req, err := svc.Client.Request(ctx, http.MethodGet, fmt.Sprintf(apiPathCluster+"/credentials", projectID, clusterName), nil) - if err != nil { - return - } - - _, err = svc.Client.LegacyDo(req, &res) - return -} - -// RotateCredentials triggers cluster credentials rotation -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#operation/SkeService_TriggerClusterCredentialRotation -func (svc *KubernetesClusterService) RotateCredentials(ctx context.Context, projectID, clusterName string) (err error) { - req, err := svc.Client.Request(ctx, http.MethodPost, fmt.Sprintf(apiPathCluster+"/rotate-credentials", projectID, clusterName), nil) - if err != nil { - return - } - - _, err = svc.Client.LegacyDo(req, nil) - return -} diff --git a/pkg/api/v1/kubernetes/clusters/clusters_test.go b/pkg/api/v1/kubernetes/clusters/clusters_test.go deleted file mode 100644 index 944f04b6..00000000 --- a/pkg/api/v1/kubernetes/clusters/clusters_test.go +++ /dev/null @@ -1,715 +0,0 @@ -package clusters_test - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" - "time" - - client "github.com/SchwarzIT/community-stackit-go-client" - "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/kubernetes" - "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/kubernetes/clusters" - "github.com/SchwarzIT/community-stackit-go-client/pkg/consts" - "github.com/SchwarzIT/community-stackit-go-client/pkg/wait" -) - -var ( - k_bad_0 = clusters.Kubernetes{} - k_bad_1 = clusters.Kubernetes{Version: "a$.b%@^*&"} - k_ok = clusters.Kubernetes{Version: "1.2.3"} - - np_bad_0 = []clusters.NodePool{} - np_bad_1 = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "type"}, - }} - np_bad_2 = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "type", Image: clusters.MachineImage{Version: "abc"}}, - }} - np_bad_3 = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "type", Image: clusters.MachineImage{Version: "abc"}}, - Minimum: 7, - Maximum: 3, - }} - np_bad_4 = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "type", Image: clusters.MachineImage{Version: "abc"}}, - Minimum: 0, - Maximum: 3, - }} - np_bad_5 = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "type", Image: clusters.MachineImage{Version: "abc"}}, - Minimum: 1, - Maximum: 101, - }} - np_bad_6 = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "type", Image: clusters.MachineImage{Version: "abc"}}, - Minimum: 1, - Maximum: 10, - MaxSurge: 0, - }} - np_bad_7 = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "type", Image: clusters.MachineImage{Version: "abc"}}, - Minimum: 1, - Maximum: 10, - MaxSurge: 2, - }} - np_bad_8 = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "type", Image: clusters.MachineImage{Version: "abc"}}, - Minimum: 1, - Maximum: 10, - MaxSurge: 2, - Volume: clusters.Volume{Size: 30}, - }} - np_bad_9 = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "type", Image: clusters.MachineImage{Version: "abc"}}, - Minimum: 1, - Maximum: 10, - MaxSurge: 2, - Volume: clusters.Volume{Size: 30}, - Taints: []clusters.Taint{{Effect: "random"}}, - }} - np_bad_10 = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "type", Image: clusters.MachineImage{Version: "abc"}}, - Minimum: 1, - Maximum: 10, - MaxSurge: 2, - Volume: clusters.Volume{Size: 30}, - Taints: []clusters.Taint{{Effect: consts.SKE_CLUSTERS_TAINT_EFFECT_NO_EXEC}}, - CRI: clusters.CRI{Name: "dockers"}, - }} - np_bad_11 = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "", Image: clusters.MachineImage{Version: "abc"}}, - Minimum: 1, - Maximum: 20, - MaxSurge: 2, - Volume: clusters.Volume{Size: 30}, - Taints: []clusters.Taint{{Effect: consts.SKE_CLUSTERS_TAINT_EFFECT_NO_EXEC, Key: "something"}}, - CRI: clusters.CRI{Name: "containerd"}, - }} - np_bad_12 = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "", Image: clusters.MachineImage{Version: "abc"}}, - Minimum: 1, - Maximum: 200, - MaxSurge: 2, - Volume: clusters.Volume{Size: 30}, - Taints: []clusters.Taint{{Effect: consts.SKE_CLUSTERS_TAINT_EFFECT_NO_EXEC, Key: "something"}}, - CRI: clusters.CRI{Name: "containerd"}, - }} - np_ok = []clusters.NodePool{{ - Machine: clusters.Machine{Type: "type", Image: clusters.MachineImage{Version: "abc"}}, - Minimum: 1, - Maximum: 10, - MaxSurge: 2, - Volume: clusters.Volume{Size: 30}, - Taints: []clusters.Taint{{Effect: consts.SKE_CLUSTERS_TAINT_EFFECT_NO_EXEC, Key: "something"}}, - CRI: clusters.CRI{Name: "containerd"}, - }} - - m_bad_1 = &clusters.Maintenance{} - m_bad_2 = &clusters.Maintenance{ - clusters.MaintenanceAutoUpdate{}, - clusters.MaintenanceTimeWindow{ - Start: "some date..", - }, - } - m_bad_3 = &clusters.Maintenance{ - clusters.MaintenanceAutoUpdate{}, - clusters.MaintenanceTimeWindow{ - End: "some date..", - }, - } - m_ok = &clusters.Maintenance{ - clusters.MaintenanceAutoUpdate{}, - clusters.MaintenanceTimeWindow{ - Start: "some date..", - End: "some other date", - }, - } - - h_ok = &clusters.Hibernation{} - h_bad_1 = &clusters.Hibernation{Schedules: []clusters.HibernationScedule{{ - Start: "something", - }}} - h_bad_2 = &clusters.Hibernation{Schedules: []clusters.HibernationScedule{{ - End: "something", - }}} - - e_ok = &clusters.Extensions{} - e_bad_1 = &clusters.Extensions{Argus: &clusters.ArgusExtension{Enabled: true, ArgusInstanceID: ""}} -) - -func TestKubernetesClusterService_CreateOrUpdate(t *testing.T) { - c, mux, teardown, _ := client.MockServer() - defer teardown() - s := kubernetes.New(c) - - projectID := "5dae0612-f5b1-4615-b7ca-b18796aa7e78" - clusterName := "cname" - - want := clusters.Cluster{ - Name: clusterName, - Kubernetes: k_ok, - Nodepools: np_ok, - Maintenance: m_ok, - Hibernation: h_ok, - } - - get1 := clusters.Cluster{ - Status: &clusters.Status{ - Aggregated: consts.SKE_CLUSTER_STATUS_HEALTHY, - }, - } - - get2 := clusters.Cluster{ - Status: &clusters.Status{ - Aggregated: consts.SKE_CLUSTER_STATUS_CREATING, - }, - } - - ctx1, cancel1 := context.WithTimeout(context.TODO(), 1*time.Second) - defer cancel1() - - ctx2, cancel2 := context.WithTimeout(context.TODO(), 2*time.Second) - defer cancel2() - - fn := func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPut { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - b, _ := json.Marshal(want) - fmt.Fprint(w, string(b)) - return - } - if r.Method == http.MethodGet { - if ctx1.Err() == nil { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusBadRequest) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - if ctx2.Err() == nil { - b, _ := json.Marshal(get2) - fmt.Fprint(w, string(b)) - return - } - - b, _ := json.Marshal(get1) - fmt.Fprint(w, string(b)) - return - } - t.Error("wrong method") - } - mux.HandleFunc("/ske/v1/projects/"+projectID+"/clusters/"+clusterName, fn) - mux.HandleFunc("/ske/v1/projects/"+projectID+"/clusters/C_N-AME", fn) - - ctx := context.Background() - ctx_bad, cancel := context.WithCancel(context.TODO()) - cancel() - - type args struct { - ctx context.Context - projectID string - clusterName string - clusterConfig clusters.Kubernetes - nodePools []clusters.NodePool - maintenance *clusters.Maintenance - hibernation *clusters.Hibernation - extensions *clusters.Extensions - } - tests := []struct { - name string - args args - wantRes clusters.Cluster - wantErr bool - useWait bool - }{ - {"ctx is canceled", args{ctx_bad, projectID, clusterName, k_ok, np_ok, m_ok, h_ok, e_ok}, want, true, false}, - {"bad project ID", args{ctx, "something", clusterName, k_ok, np_ok, m_ok, h_ok, e_ok}, want, true, false}, - {"bad cluster name", args{ctx, projectID, "C_N-AME", k_ok, np_ok, m_ok, h_ok, e_ok}, want, true, false}, - {"all ok", args{ctx, projectID, clusterName, k_ok, np_ok, m_ok, h_ok, e_ok}, want, false, true}, - {"all ok 2", args{ctx, projectID, clusterName, k_ok, np_ok, nil, h_ok, e_ok}, want, false, false}, - {"all ok 3", args{ctx, projectID, clusterName, k_ok, np_ok, nil, nil, e_ok}, want, false, false}, - {"all ok 4", args{ctx, projectID, clusterName, k_ok, np_ok, nil, nil, nil}, want, false, false}, - - {"kube bad 0", args{ctx, projectID, clusterName, k_bad_0, np_ok, m_ok, h_ok, e_ok}, want, true, false}, - {"kube bad 1", args{ctx, projectID, clusterName, k_bad_1, np_ok, m_ok, h_ok, e_ok}, want, true, false}, - - {"np bad 0", args{ctx, projectID, clusterName, k_ok, np_bad_0, m_ok, h_ok, e_ok}, want, true, false}, - {"np bad 1", args{ctx, projectID, clusterName, k_ok, np_bad_1, m_ok, h_ok, e_ok}, want, true, false}, - {"np bad 2", args{ctx, projectID, clusterName, k_ok, np_bad_2, m_ok, h_ok, e_ok}, want, true, false}, - {"np bad 3", args{ctx, projectID, clusterName, k_ok, np_bad_3, m_ok, h_ok, e_ok}, want, true, false}, - {"np bad 4", args{ctx, projectID, clusterName, k_ok, np_bad_4, m_ok, h_ok, e_ok}, want, true, false}, - {"np bad 5", args{ctx, projectID, clusterName, k_ok, np_bad_5, m_ok, h_ok, e_ok}, want, true, false}, - {"np bad 6", args{ctx, projectID, clusterName, k_ok, np_bad_6, m_ok, h_ok, e_ok}, want, true, false}, - {"np bad 7", args{ctx, projectID, clusterName, k_ok, np_bad_7, m_ok, h_ok, e_ok}, want, true, false}, - {"np bad 8", args{ctx, projectID, clusterName, k_ok, np_bad_8, m_ok, h_ok, e_ok}, want, true, false}, - {"np bad 9", args{ctx, projectID, clusterName, k_ok, np_bad_9, m_ok, h_ok, e_ok}, want, true, false}, - {"np bad 10", args{ctx, projectID, clusterName, k_ok, np_bad_10, m_ok, h_ok, e_ok}, want, true, false}, - {"np bad 11", args{ctx, projectID, clusterName, k_ok, np_bad_11, m_ok, h_ok, e_ok}, want, true, false}, - {"np bad 12", args{ctx, projectID, clusterName, k_ok, np_bad_12, m_ok, h_ok, e_ok}, want, true, false}, - - {"maintenance bad 1", args{ctx, projectID, clusterName, k_ok, np_ok, m_bad_1, h_ok, e_ok}, want, true, false}, - {"maintenance bad 2", args{ctx, projectID, clusterName, k_ok, np_ok, m_bad_2, h_ok, e_ok}, want, true, false}, - {"maintenance bad 3", args{ctx, projectID, clusterName, k_ok, np_ok, m_bad_3, h_ok, e_ok}, want, true, false}, - - {"h bad 1", args{ctx, projectID, clusterName, k_ok, np_ok, m_ok, h_bad_1, e_ok}, want, true, false}, - {"h bad 2", args{ctx, projectID, clusterName, k_ok, np_ok, m_ok, h_bad_2, e_ok}, want, true, false}, - - {"e bad 1", args{ctx, projectID, clusterName, k_ok, np_ok, m_ok, h_ok, e_bad_1}, want, true, false}, - } - - var process *wait.Handler - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // test create - { - gotRes, w, err := s.Clusters.CreateOrUpdate(tt.args.ctx, tt.args.projectID, tt.args.clusterName, tt.args.clusterConfig, tt.args.nodePools, tt.args.maintenance, tt.args.hibernation, tt.args.extensions) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesClusterService.Create() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) && !tt.wantErr { - t.Errorf("KubernetesClusterService.Create() = %v, want %v", gotRes, tt.wantRes) - } - - if tt.useWait { - process = w - } - } - }) - } - - process.SetThrottle(1 * time.Second) - - // during the initial call, the server is supposed to return an error - // that should force the Wait() to exit immediately - if _, err := process.Wait(); err == nil { - t.Errorf("expected error during first process wait, got nil instead") - } - - // after 1s the server should return a retryable error and than - // change to the wanted response status - // meaning - Wait() should exit without any error - time.Sleep(1 * time.Second) - if _, err := process.Wait(); err != nil { - t.Errorf("unexpected error during 2nd process wait: %s", err) - } -} - -func TestKubernetesClusterService_List(t *testing.T) { - c, mux, teardown, _ := client.MockServer() - defer teardown() - s := kubernetes.New(c) - - projectID := "5dae0612-f5b1-4615-b7ca-b18796aa7e78" - clusterName := "cname" - want := clusters.ClusterList{ - Items: []clusters.Cluster{{ - Name: clusterName, - Kubernetes: k_ok, - Nodepools: np_ok, - Maintenance: m_ok, - Hibernation: h_ok, - }}, - } - - mux.HandleFunc("/ske/v1/projects/"+projectID+"/clusters", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - t.Error("wrong method") - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - b, _ := json.Marshal(want) - fmt.Fprint(w, string(b)) - }) - - ctx := context.Background() - ctx_bad, cancel := context.WithCancel(context.TODO()) - cancel() - - type args struct { - ctx context.Context - projectID string - } - tests := []struct { - name string - args args - wantRes clusters.ClusterList - wantErr bool - }{ - {"ctx is canceled", args{ctx_bad, projectID}, want, true}, - {"bad project ID", args{ctx, "something"}, want, true}, - {"all ok", args{ctx, projectID}, want, false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotRes, err := s.Clusters.List(tt.args.ctx, tt.args.projectID) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesClusterService.List() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) && !tt.wantErr { - t.Errorf("KubernetesClusterService.List() = %v, want %v", gotRes, tt.wantRes) - } - }) - } -} - -func TestKubernetesClusterService_Get(t *testing.T) { - c, mux, teardown, _ := client.MockServer() - defer teardown() - s := kubernetes.New(c) - - projectID := "5dae0612-f5b1-4615-b7ca-b18796aa7e78" - clusterName := "cname" - - want := clusters.Cluster{ - Name: clusterName, - Kubernetes: k_ok, - Nodepools: np_ok, - Maintenance: m_ok, - Hibernation: h_ok, - } - - mux.HandleFunc("/ske/v1/projects/"+projectID+"/clusters/"+clusterName, func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - t.Error("wrong method") - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - b, _ := json.Marshal(want) - fmt.Fprint(w, string(b)) - }) - - ctx := context.Background() - ctx_bad, cancel := context.WithCancel(context.TODO()) - cancel() - type args struct { - ctx context.Context - projectID string - clusterName string - } - tests := []struct { - name string - args args - wantRes clusters.Cluster - wantErr bool - }{ - {"ctx is canceled", args{ctx_bad, projectID, clusterName}, want, true}, - {"bad project ID", args{ctx, "something", clusterName}, want, true}, - {"bad cluster name", args{ctx, projectID, "something"}, want, true}, - {"all ok", args{ctx, projectID, clusterName}, want, false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotRes, err := s.Clusters.Get(tt.args.ctx, tt.args.projectID, tt.args.clusterName) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesClusterService.Get() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) && !tt.wantErr { - t.Errorf("KubernetesClusterService.Get() = %v, want %v", gotRes, tt.wantRes) - } - }) - } -} - -func TestKubernetesClusterService_Delete(t *testing.T) { - c, mux, teardown, _ := client.MockServer() - defer teardown() - s := kubernetes.New(c) - - projectID := "5dae0612-f5b1-4615-b7ca-b18796aa7e78" - clusterName := "cname" - - get1 := clusters.Cluster{ - Status: &clusters.Status{ - Aggregated: consts.SKE_CLUSTER_STATUS_HEALTHY, - }, - } - - ctx1, cancel1 := context.WithTimeout(context.TODO(), 1*time.Second) - defer cancel1() - - ctx2, cancel2 := context.WithTimeout(context.TODO(), 2*time.Second) - defer cancel2() - - mux.HandleFunc("/ske/v1/projects/"+projectID+"/clusters/"+clusterName, func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodDelete { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - return - } - if r.Method == http.MethodGet { - w.Header().Set("Content-Type", "application/json") - if ctx1.Err() == nil { - w.WriteHeader(http.StatusBadRequest) - return - } - if ctx2.Err() == nil { - w.WriteHeader(http.StatusOK) - b, _ := json.Marshal(get1) - fmt.Fprint(w, string(b)) - return - } - - w.WriteHeader(http.StatusNotFound) - return - } - t.Error("wrong method") - }) - - ctx := context.Background() - ctx_bad, cancel := context.WithCancel(context.TODO()) - cancel() - - type args struct { - ctx context.Context - projectID string - clusterName string - } - tests := []struct { - name string - args args - wantErr bool - useWait bool - }{ - {"ctx is canceled", args{ctx_bad, projectID, clusterName}, true, false}, - {"bad project ID", args{ctx, "something", clusterName}, true, false}, - {"bad cluster name", args{ctx, projectID, "something"}, true, false}, - {"all ok", args{ctx, projectID, clusterName}, false, true}, - } - var process *wait.Handler - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - w, err := s.Clusters.Delete(tt.args.ctx, tt.args.projectID, tt.args.clusterName) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesClusterService.Delete() error = %v, wantErr %v", err, tt.wantErr) - } - if tt.useWait { - process = w - } - }) - } - - // during the initial call, the server is supposed to return an error - // that should force the Wait() to exist immediately - if _, err := process.Wait(); err == nil { - t.Error("expected error in first attempt, but got nil instead") - } - - // after 1s the server should return a status of an active cluster - // and after another wait run return status Not Found -> cluster deleted - // meaning - Wait() should exit without any error - time.Sleep(1 * time.Second) - if _, err := process.Wait(); err != nil { - t.Errorf("unexpected error during 2nd wait: %v", err) - } -} - -func TestKubernetesClusterService_Triggers(t *testing.T) { - c, mux, teardown, _ := client.MockServer() - defer teardown() - s := kubernetes.New(c) - - projectID := "5dae0612-f5b1-4615-b7ca-b18796aa7e78" - clusterName := "cname" - - want := clusters.Cluster{ - Name: clusterName, - Kubernetes: k_ok, - Nodepools: np_ok, - Maintenance: m_ok, - Hibernation: h_ok, - } - - fixedRespFn := func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - t.Error("wrong method") - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - b, _ := json.Marshal(want) - fmt.Fprint(w, string(b)) - } - - mux.HandleFunc("/ske/v1/projects/"+projectID+"/clusters/"+clusterName+"/hibernate", fixedRespFn) - mux.HandleFunc("/ske/v1/projects/"+projectID+"/clusters/"+clusterName+"/maintenance", fixedRespFn) - mux.HandleFunc("/ske/v1/projects/"+projectID+"/clusters/"+clusterName+"/reconcile", fixedRespFn) - mux.HandleFunc("/ske/v1/projects/"+projectID+"/clusters/"+clusterName+"/wakeup", fixedRespFn) - - ctx := context.Background() - ctx_bad, cancel := context.WithCancel(context.TODO()) - cancel() - - type args struct { - ctx context.Context - projectID string - clusterName string - } - tests := []struct { - name string - args args - wantRes clusters.Cluster - wantErr bool - }{ - {"ctx is canceled", args{ctx_bad, projectID, clusterName}, want, true}, - {"bad project ID", args{ctx, "something", clusterName}, want, true}, - {"bad cluster name", args{ctx, projectID, "something"}, want, true}, - {"all ok", args{ctx, projectID, clusterName}, want, false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Hibernate - { - gotRes, err := s.Clusters.Hibernate(tt.args.ctx, tt.args.projectID, tt.args.clusterName) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesClusterService.Hibernate() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) && !tt.wantErr { - t.Errorf("KubernetesClusterService.Hibernate() = %v, want %v", gotRes, tt.wantRes) - } - } - // maintenance - { - gotRes, err := s.Clusters.Maintenance(tt.args.ctx, tt.args.projectID, tt.args.clusterName) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesClusterService.Maintenance() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) && !tt.wantErr { - t.Errorf("KubernetesClusterService.Maintenance() = %v, want %v", gotRes, tt.wantRes) - } - } - // Reconcile - { - gotRes, err := s.Clusters.Reconcile(tt.args.ctx, tt.args.projectID, tt.args.clusterName) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesClusterService.Reconcile() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) && !tt.wantErr { - t.Errorf("KubernetesClusterService.Reconcile() = %v, want %v", gotRes, tt.wantRes) - } - } - // Wakeup - { - gotRes, err := s.Clusters.Wakeup(tt.args.ctx, tt.args.projectID, tt.args.clusterName) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesClusterService.Wakeup() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) && !tt.wantErr { - t.Errorf("KubernetesClusterService.Wakeup() = %v, want %v", gotRes, tt.wantRes) - } - } - }) - } -} - -func TestKubernetesClusterService_GetCredential(t *testing.T) { - c, mux, teardown, _ := client.MockServer() - defer teardown() - s := kubernetes.New(c) - - projectID := "5dae0612-f5b1-4615-b7ca-b18796aa7e78" - clusterName := "cname" - - want := clusters.Credentials{} - - mux.HandleFunc("/ske/v1/projects/"+projectID+"/clusters/"+clusterName+"/credentials", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - t.Error("wrong method") - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - b, _ := json.Marshal(want) - fmt.Fprint(w, string(b)) - }) - - ctx := context.Background() - ctx_bad, cancel := context.WithCancel(context.TODO()) - cancel() - - type args struct { - ctx context.Context - projectID string - clusterName string - } - tests := []struct { - name string - args args - wantRes clusters.Credentials - wantErr bool - }{ - {"ctx is canceled", args{ctx_bad, projectID, clusterName}, want, true}, - {"bad project ID", args{ctx, "something", clusterName}, want, true}, - {"bad cluster name", args{ctx, projectID, "something"}, want, true}, - {"all ok", args{ctx, projectID, clusterName}, want, false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotRes, err := s.Clusters.GetCredential(tt.args.ctx, tt.args.projectID, tt.args.clusterName) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesClusterService.GetCredential() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) { - t.Errorf("KubernetesClusterService.GetCredential() = %v, want %v", gotRes, tt.wantRes) - } - }) - } -} - -func TestKubernetesClusterService_RotateCredentials(t *testing.T) { - c, mux, teardown, _ := client.MockServer() - defer teardown() - s := kubernetes.New(c) - - projectID := "5dae0612-f5b1-4615-b7ca-b18796aa7e78" - clusterName := "cname" - mux.HandleFunc("/ske/v1/projects/"+projectID+"/clusters/"+clusterName+"/rotate-credentials", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - t.Error("wrong method") - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - }) - - ctx := context.Background() - ctx_bad, cancel := context.WithCancel(context.TODO()) - cancel() - - type args struct { - ctx context.Context - projectID string - clusterName string - } - tests := []struct { - name string - args args - wantErr bool - }{ - {"ctx is canceled", args{ctx_bad, projectID, clusterName}, true}, - {"bad project ID", args{ctx, "something", clusterName}, true}, - {"bad cluster name", args{ctx, projectID, "something"}, true}, - {"all ok", args{ctx, projectID, clusterName}, false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := s.Clusters.RotateCredentials(tt.args.ctx, tt.args.projectID, tt.args.clusterName); (err != nil) != tt.wantErr { - t.Errorf("KubernetesClusterService.RotateCredentials() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/pkg/api/v1/kubernetes/clusters/validate.go b/pkg/api/v1/kubernetes/clusters/validate.go deleted file mode 100644 index f97acb7c..00000000 --- a/pkg/api/v1/kubernetes/clusters/validate.go +++ /dev/null @@ -1,164 +0,0 @@ -// this file is used for validating cluster data and properties - -package clusters - -import ( - "errors" - "fmt" - "regexp" - - "github.com/SchwarzIT/community-stackit-go-client/pkg/consts" - "github.com/SchwarzIT/community-stackit-go-client/pkg/validate" -) - -// ValidateCluster validates the given cluster data (dry validation) -func ValidateCluster( - clusterName string, - clusterConfig Kubernetes, - nodePools []NodePool, - maintenance *Maintenance, - hibernation *Hibernation, - extensions *Extensions, -) error { - if err := validate.SemVer(clusterConfig.Version); err != nil { - return err - } - if err := ValidateClusterName(clusterName); err != nil { - return err - } - if len(nodePools) == 0 { - return errors.New("at least one node pool must be specified") - } - for _, np := range nodePools { - if err := ValidateNodePool(np); err != nil { - return err - } - } - if err := ValidateMaintenance(maintenance); err != nil { - return err - } - if err := ValidateHibernation(hibernation); err != nil { - return err - } - if err := ValidateExtensions(extensions); err != nil { - return err - } - return nil -} - -// ValidateClusterName validates a given cluster name -func ValidateClusterName(name string) error { - exp := `^[a-z0-9-]{1,11}$` - r := regexp.MustCompile(exp) - if !r.MatchString(name) { - return fmt.Errorf("invalid cluster name. valid name is of: %s", exp) - } - return nil -} - -// ValidateNodePool validates a given node pool -func ValidateNodePool(np NodePool) error { - if np.Machine.Type == "" { - return errors.New("machine type must be specified") - } - if np.Machine.Image.Version == "" { - return errors.New("machine image version must be specified") - } - if np.Minimum > np.Maximum { - return errors.New("minimum value can't be larger than maximum") - } - if np.Minimum < 1 || np.Minimum > 100 { - return errors.New("minimum value must be in the range of 1..100") - } - if np.Maximum < 1 || np.Maximum > 100 { - return errors.New("maximum value must be in the range of 1..100") - } - if np.MaxSurge < 1 || np.Maximum > 10 { - return errors.New("max surge value must be in the range of 1..10") - } - if np.Volume.Size < 20 || np.Volume.Size > 10240 { - return errors.New("volume size value must be in the range of 20..10240") - } - for _, t := range np.Taints { - if err := ValidateTaint(t); err != nil { - return err - } - } - if err := ValidateCRI(np.CRI); err != nil { - return err - } - return nil -} - -// ValidateTaint validates a given node pool taint -func ValidateTaint(t Taint) error { - switch t.Effect { - case consts.SKE_CLUSTERS_TAINT_EFFECT_NO_EXEC: - fallthrough - case consts.SKE_CLUSTERS_TAINT_EFFECT_NO_SCHED: - fallthrough - case consts.SKE_CLUSTERS_TAINT_EFFECT_PREF_NO_SCHED: - default: - return fmt.Errorf("invalid taint effect '%s'", t.Effect) - } - - if t.Key == "" { - return errors.New("taint key is required") - } - return nil -} - -// ValidateCRI validates the given cri struct -func ValidateCRI(c CRI) error { - switch c.Name { - case consts.SKE_CLUSTERS_CRI_CONTAINERD: - fallthrough - case consts.SKE_CLUSTERS_CRI_DOCKER: - default: - return fmt.Errorf("invalid CRI name '%s'", c.Name) - } - return nil -} - -// ValidateMaintenance validates a given cluster maintenance -func ValidateMaintenance(m *Maintenance) error { - if m == nil { - return nil - } - if m.TimeWindow.End == "" { - return errors.New("maintenance end time window is required") - } - if m.TimeWindow.Start == "" { - return errors.New("maintenance start time window is required") - } - return nil -} - -// ValidateHibernation validates a given cluster hibernation -func ValidateHibernation(h *Hibernation) error { - if h == nil { - return nil - } - for _, s := range h.Schedules { - if s.End == "" { - return errors.New("hibernation end time is required") - } - if s.Start == "" { - return errors.New("hibernation start time is required") - } - } - return nil -} - -// ValidateExtensions validates a given cluster extensions -func ValidateExtensions(e *Extensions) error { - if e == nil { - return nil - } - if e.Argus != nil { - if e.Argus.Enabled && e.Argus.ArgusInstanceID == "" { - return errors.New("argus instance ID is mandatory when Argus is enabled") - } - } - return nil -} diff --git a/pkg/api/v1/kubernetes/kubernetes.go b/pkg/api/v1/kubernetes/kubernetes.go deleted file mode 100644 index 16fad10a..00000000 --- a/pkg/api/v1/kubernetes/kubernetes.go +++ /dev/null @@ -1,28 +0,0 @@ -// package kubernetes groups together STACKIT Kubernetes Engine (SKE) related functionalities -// such as cluster management, options retrieval and enabling SKE in projects - -package kubernetes - -import ( - "github.com/SchwarzIT/community-stackit-go-client/internal/common" - "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/kubernetes/clusters" - "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/kubernetes/options" - "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/kubernetes/projects" -) - -// New returns a new handler for the service -func New(c common.Client) *KubernetesService { - return &KubernetesService{ - Clusters: clusters.New(c), - Options: options.New(c), - Projects: projects.New(c), - } -} - -// KubernetesService is the service that handles -// SKE related services, such as clusters, credentials & operations -type KubernetesService struct { - Clusters *clusters.KubernetesClusterService - Options *options.KubernetesOptionsService - Projects *projects.KubernetesProjectsService -} diff --git a/pkg/api/v1/kubernetes/options/options.go b/pkg/api/v1/kubernetes/options/options.go deleted file mode 100644 index 9bf94c27..00000000 --- a/pkg/api/v1/kubernetes/options/options.go +++ /dev/null @@ -1,81 +0,0 @@ -// package options is used to retrieve various options used for configuring a SKE cluster -// Such as available Kubernetes versions, machine types and more - -package options - -import ( - "context" - "net/http" - - "github.com/SchwarzIT/community-stackit-go-client/internal/common" - "github.com/SchwarzIT/community-stackit-go-client/pkg/consts" -) - -// constants -const ( - apiPath = consts.API_PATH_SKE_OPTIONS -) - -// New returns a new handler for the service -func New(c common.Client) *KubernetesOptionsService { - return &KubernetesOptionsService{ - Client: c, - } -} - -// KubernetesOptionsService is the service that retrieves the provider options -type KubernetesOptionsService common.Service - -// ProviderOptions is the api's provider options response struct -type ProviderOptions struct { - KubernetesVersions []KubernetesVersion `json:"kubernetesVersions"` - MachineTypes []MachineType `json:"machineTypes"` - MachineImages []MachineImage `json:"machineImages"` - VolumeTypes []VolumeType `json:"volumeTypes"` - AvailabilityZones []AvailabilityZone `json:"availabilityZones"` -} - -type KubernetesVersion struct { - Version string `json:"version"` - State string `json:"state"` - ExpirationDate string `json:"expirationDate"` - FeatureGates map[string]string `json:"featureGates"` -} - -type MachineType struct { - Name string `json:"name"` - CPU int `json:"cpu"` - Memory int `json:"memory"` -} - -type MachineImage struct { - Name string `json:"name"` - Versions []struct { - Version string `json:"version"` - State string `json:"state"` - ExpirationDate string `json:"expirationDate"` - CRI []struct { - Name string `json:"name"` - } `json:"cri"` - } `json:"versions"` -} - -type VolumeType struct { - Name string `json:"name"` -} - -type AvailabilityZone struct { - Name string `json:"name"` -} - -// List returns all of the SKE provider options -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#tag/ProviderOptions -func (svc *KubernetesOptionsService) List(ctx context.Context) (res ProviderOptions, err error) { - req, err := svc.Client.Request(ctx, http.MethodGet, apiPath, nil) - if err != nil { - return - } - - _, err = svc.Client.LegacyDo(req, &res) - return -} diff --git a/pkg/api/v1/kubernetes/options/options_test.go b/pkg/api/v1/kubernetes/options/options_test.go deleted file mode 100644 index e73c0072..00000000 --- a/pkg/api/v1/kubernetes/options/options_test.go +++ /dev/null @@ -1,107 +0,0 @@ -package options_test - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" - - "github.com/SchwarzIT/community-stackit-go-client" - "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/kubernetes/options" -) - -var optionsExample = `{ - "kubernetesVersions": [ - { - "version": "string", - "state": "string", - "expirationDate": "2019-08-24T14:15:22Z", - "featureGates": { - "property1": "string", - "property2": "string" - } - } - ], - "machineTypes": [ - { - "name": "string", - "cpu": 0, - "memory": 0 - } - ], - "machineImages": [ - { - "name": "string", - "versions": [ - { - "version": "string", - "state": "string", - "expirationDate": "2019-08-24T14:15:22Z", - "cri": [ - { - "name": "docker" - } - ] - } - ] - } - ], - "volumeTypes": [ - { - "name": "string" - } - ], - "availabilityZones": [ - { - "name": "string" - } - ] - }` - -func TestKubernetesOptionsService_List(t *testing.T) { - c, mux, teardown, _ := client.MockServer() - defer teardown() - o := options.New(c) - - var want options.ProviderOptions - if err := json.Unmarshal([]byte(optionsExample), &want); err != nil { - t.Errorf(err.Error()) - } - mux.HandleFunc("/ske/v1/provider-options", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - t.Error("wrong method") - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, optionsExample) - }) - - ctx, cancel := context.WithCancel(context.TODO()) - cancel() - type args struct { - ctx context.Context - } - tests := []struct { - name string - args args - wantRes options.ProviderOptions - wantErr bool - }{ - {"all ok", args{context.Background()}, want, false}, - {"ctx is canceled", args{ctx}, want, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotRes, err := o.List(tt.args.ctx) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesOptionsService.List() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) && !tt.wantErr { - t.Errorf("KubernetesOptionsService.List() = %v, want %v", gotRes, tt.wantRes) - } - }) - } -} diff --git a/pkg/api/v1/kubernetes/projects/project.go b/pkg/api/v1/kubernetes/projects/project.go deleted file mode 100644 index f111f5ff..00000000 --- a/pkg/api/v1/kubernetes/projects/project.go +++ /dev/null @@ -1,117 +0,0 @@ -// package projects enables/disables SKE usage in STACKIT projects -// IMPORTANT: disabling SKE will cause existing clusters to be automatically deleted - -package projects - -import ( - "context" - "fmt" - "net/http" - "strings" - - "github.com/SchwarzIT/community-stackit-go-client/internal/common" - "github.com/SchwarzIT/community-stackit-go-client/pkg/consts" - "github.com/SchwarzIT/community-stackit-go-client/pkg/wait" -) - -// constants -const ( - apiPath = consts.API_PATH_SKE_PROJECTS -) - -// New returns a new handler for the service -func New(c common.Client) *KubernetesProjectsService { - return &KubernetesProjectsService{ - Client: c, - } -} - -// KubernetesProjectsService is the service that handles -// enabling / disabling SKE for a project -type KubernetesProjectsService common.Service - -// KubernetesProjectsResponse is the project ID and state response -type KubernetesProjectsResponse struct { - ProjectID string `json:"projectId"` - State string `json:"state"` -} - -// Get returns the SKE project status -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#operation/SkeService_GetProject -func (svc *KubernetesProjectsService) Get(ctx context.Context, projectID string) (res KubernetesProjectsResponse, err error) { - req, err := svc.Client.Request(ctx, http.MethodGet, fmt.Sprintf(apiPath, projectID), nil) - if err != nil { - return - } - - _, err = svc.Client.LegacyDo(req, &res) - return -} - -// Create creates a SKE project -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#operation/SkeService_GetProject -func (svc *KubernetesProjectsService) Create(ctx context.Context, projectID string) (res KubernetesProjectsResponse, w *wait.Handler, err error) { - req, err := svc.Client.Request(ctx, http.MethodPut, fmt.Sprintf(apiPath, projectID), nil) - if err != nil { - return - } - - _, err = svc.Client.LegacyDo(req, &res) - w = wait.New(svc.waitForCreation(ctx, projectID)) - return -} - -// waitForCreation returns a wait function to determine if kubernetes project has been created -func (svc *KubernetesProjectsService) waitForCreation(ctx context.Context, projectID string) wait.WaitFn { - return func() (res interface{}, done bool, err error) { - res, err = svc.Get(ctx, projectID) - if err != nil { - if strings.Contains(err.Error(), "project has no assigned namespace") { - return nil, false, nil - } - return nil, false, err - } - project := res.(KubernetesProjectsResponse) - switch project.State { - case consts.SKE_PROJECT_STATUS_FAILED: - fallthrough - case consts.SKE_PROJECT_STATUS_DELETING: - err = fmt.Errorf("received state: %s for project ID: %s", - project.State, - project.ProjectID, - ) - return - case consts.SKE_PROJECT_STATUS_CREATED: - return nil, true, nil - } - return nil, false, nil - } -} - -// Delete deletes a SKE project -// IMPORTANT: existing clusters to be automatically deleted -// See also https://api.stackit.schwarz/ske-service/openapi.v1.html#operation/SkeService_GetProject -func (svc *KubernetesProjectsService) Delete(ctx context.Context, projectID string) (w *wait.Handler, err error) { - req, err := svc.Client.Request(ctx, http.MethodDelete, fmt.Sprintf(apiPath, projectID), nil) - if err != nil { - return - } - - _, err = svc.Client.LegacyDo(req, nil) - w = wait.New(svc.waitForDeletion(ctx, projectID)) - return -} - -// waitForDeletion returns a wait function to determine if kubernetes project has been deleted -func (svc *KubernetesProjectsService) waitForDeletion(ctx context.Context, projectID string) wait.WaitFn { - return func() (res interface{}, done bool, err error) { - res, err = svc.Get(ctx, projectID) - if err != nil { - if strings.Contains(err.Error(), http.StatusText(http.StatusNotFound)) { - return nil, true, nil - } - return nil, false, err - } - return nil, false, nil - } -} diff --git a/pkg/api/v1/kubernetes/projects/project_test.go b/pkg/api/v1/kubernetes/projects/project_test.go deleted file mode 100644 index b75b68cd..00000000 --- a/pkg/api/v1/kubernetes/projects/project_test.go +++ /dev/null @@ -1,290 +0,0 @@ -package projects_test - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" - "time" - - client "github.com/SchwarzIT/community-stackit-go-client" - "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/kubernetes" - "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/kubernetes/projects" - "github.com/SchwarzIT/community-stackit-go-client/pkg/consts" - "github.com/SchwarzIT/community-stackit-go-client/pkg/wait" -) - -func TestKubernetesProjectsService_Get(t *testing.T) { - c, mux, teardown, _ := client.MockServer() - defer teardown() - s := kubernetes.New(c) - - projectID := "5dae0612-f5b1-4615-b7ca-b18796aa7e78" - - want := projects.KubernetesProjectsResponse{ - ProjectID: projectID, - State: consts.SKE_PROJECT_STATUS_UNSPECIFIED, - } - - mux.HandleFunc("/ske/v1/projects/"+projectID, func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - t.Error("wrong method") - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - b, _ := json.Marshal(want) - fmt.Fprint(w, string(b)) - }) - - ctx, cancel := context.WithCancel(context.TODO()) - cancel() - - type args struct { - ctx context.Context - projectID string - } - tests := []struct { - name string - args args - wantRes projects.KubernetesProjectsResponse - wantErr bool - }{ - {"all ok", args{context.Background(), projectID}, want, false}, - {"ctx is canceled", args{ctx, projectID}, want, true}, - {"project not found", args{context.Background(), "my-project"}, want, true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotRes, err := s.Projects.Get(tt.args.ctx, tt.args.projectID) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesProjectsService.Get() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) && !tt.wantErr { - t.Errorf("KubernetesProjectsService.Get() = %v, want %v", gotRes, tt.wantRes) - } - }) - } -} - -func TestKubernetesProjectsService_Create(t *testing.T) { - c, mux, teardown, _ := client.MockServer() - defer teardown() - s := kubernetes.New(c) - - projectID := "5dae0612-f5b1-4615-b7ca-b18796aa7e78" - - want := projects.KubernetesProjectsResponse{ - ProjectID: projectID, - State: consts.SKE_PROJECT_STATUS_UNSPECIFIED, - } - - ctx2, cancel2 := context.WithTimeout(context.TODO(), 1*time.Second) - defer cancel2() - - ctx3, cancel3 := context.WithTimeout(context.TODO(), 3*time.Second) - defer cancel3() - - ctx4, cancel4 := context.WithTimeout(context.TODO(), 2*time.Second) - defer cancel4() - - state1 := projects.KubernetesProjectsResponse{ - ProjectID: projectID, - State: consts.SKE_PROJECT_STATUS_UNSPECIFIED, - } - - state2 := projects.KubernetesProjectsResponse{ - ProjectID: projectID, - State: consts.SKE_PROJECT_STATUS_CREATED, - } - - state3 := projects.KubernetesProjectsResponse{ - ProjectID: projectID, - State: consts.SKE_PROJECT_STATUS_FAILED, - } - - mux.HandleFunc("/ske/v1/projects/"+projectID, func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodPut { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - b, _ := json.Marshal(want) - fmt.Fprint(w, string(b)) - return - } - if r.Method == http.MethodGet { - w.Header().Set("Content-Type", "application/json") - - if ctx2.Err() == nil { - w.WriteHeader(http.StatusOK) - b, _ := json.Marshal(state1) - fmt.Fprint(w, string(b)) - return - } - - if ctx4.Err() == nil { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprint(w, `{"code":"InvalidArgument", "message":"project has no assigned namespace", "details":""}`) - return - } - - if ctx3.Err() == nil { - w.WriteHeader(http.StatusOK) - b, _ := json.Marshal(state2) - fmt.Fprint(w, string(b)) - return - } - - w.WriteHeader(http.StatusOK) - b, _ := json.Marshal(state3) - fmt.Fprint(w, string(b)) - return - } - t.Error("wrong method") - }) - - ctx, cancel := context.WithCancel(context.TODO()) - cancel() - - type args struct { - ctx context.Context - projectID string - } - tests := []struct { - name string - args args - wantRes projects.KubernetesProjectsResponse - wantErr bool - goodWait bool - }{ - {"all ok", args{context.Background(), projectID}, want, false, true}, - {"ctx is canceled", args{ctx, projectID}, want, true, false}, - {"project not found", args{context.Background(), "my-project"}, want, true, false}, - } - - var goodWait, badWait *wait.Handler - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotRes, w, err := s.Projects.Create(tt.args.ctx, tt.args.projectID) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesProjectsService.Create() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotRes, tt.wantRes) && !tt.wantErr { - t.Errorf("KubernetesProjectsService.Create() = %v, want %v", gotRes, tt.wantRes) - } - if tt.goodWait { - goodWait = w - } else { - badWait = w - } - }) - } - - goodWait.SetThrottle(20 * time.Millisecond) - badWait.SetThrottle(20 * time.Millisecond) - - if _, err := goodWait.Wait(); err != nil { - t.Errorf("returned: %v", err) - } - if _, err := badWait.Wait(); err == nil { - t.Error("expected error but got nil") - } - - time.Sleep(2 * time.Second) - if _, err := goodWait.Wait(); err == nil { - t.Error("expected error but got nil [2]") - } - -} - -func TestKubernetesProjectsService_Delete(t *testing.T) { - c, mux, teardown, _ := client.MockServer() - defer teardown() - s := kubernetes.New(c) - - projectID := "5dae0612-f5b1-4615-b7ca-b18796aa7e78" - - ctx, cancel := context.WithCancel(context.TODO()) - cancel() - - ctx2, cancel2 := context.WithTimeout(context.TODO(), 1*time.Second) - defer cancel2() - - ctx3, cancel3 := context.WithTimeout(context.TODO(), 3*time.Second) - defer cancel3() - - state1 := projects.KubernetesProjectsResponse{ - ProjectID: projectID, - State: consts.SKE_PROJECT_STATUS_DELETING, - } - - mux.HandleFunc("/ske/v1/projects/"+projectID, func(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodDelete { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - return - } - if r.Method == http.MethodGet { - w.Header().Set("Content-Type", "application/json") - - if ctx2.Err() == nil { - w.WriteHeader(http.StatusOK) - b, _ := json.Marshal(state1) - fmt.Fprint(w, string(b)) - return - } - - if ctx3.Err() == nil { - w.WriteHeader(http.StatusNotFound) - return - } - - w.WriteHeader(http.StatusBadRequest) - return - } - t.Error("wrong method") - }) - - type args struct { - ctx context.Context - projectID string - } - tests := []struct { - name string - args args - wantErr bool - goodWait bool - }{ - {"all ok", args{context.Background(), projectID}, false, true}, - {"ctx is canceled", args{ctx, projectID}, true, false}, - {"project not found", args{context.Background(), "my-project"}, true, false}, - } - - var goodWait *wait.Handler - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - w, err := s.Projects.Delete(tt.args.ctx, tt.args.projectID) - if (err != nil) != tt.wantErr { - t.Errorf("KubernetesProjectsService.Delete() error = %v, wantErr %v", err, tt.wantErr) - } - if tt.goodWait { - goodWait = w - } - }) - } - - goodWait.SetThrottle(20 * time.Millisecond) - - if _, err := goodWait.Wait(); err != nil { - t.Errorf("returned: %v", err) - } - time.Sleep(2 * time.Second) - if _, err := goodWait.Wait(); err == nil { - t.Error("expected error but got nil") - } - -} diff --git a/pkg/consts/apis.go b/pkg/consts/apis.go index 464ef0d8..86c77a7a 100644 --- a/pkg/consts/apis.go +++ b/pkg/consts/apis.go @@ -20,13 +20,6 @@ const ( API_PATH_COSTS_PROJECT = "/costs-service/v1/costs/%s/projects/%s" API_PATH_COSTS_PROJECT_WITH_PARAMS = API_PATH_COSTS_PROJECT + "?from=%s&to=%s&granularity=%v&depth=%v" - // Kubernetes - API_PATH_SKE = "/ske/v1" - API_PATH_SKE_PROJECTS = API_PATH_SKE + "/projects/%s" - API_PATH_SKE_CLUSTERS = API_PATH_SKE_PROJECTS + "/clusters" - API_PATH_SKE_WITH_CLUSTER_ID = API_PATH_SKE_CLUSTERS + "/%s" - API_PATH_SKE_OPTIONS = API_PATH_SKE + "/provider-options" - // Object Storage API_PATH_OBJECT_STORAGE = "/object-storage-api/v1" API_PATH_OBJECT_STORAGE_PROJECT = API_PATH_OBJECT_STORAGE + "/project/%s" diff --git a/pkg/consts/consts.go b/pkg/consts/consts.go index 5d14f6ab..6de9dbc4 100644 --- a/pkg/consts/consts.go +++ b/pkg/consts/consts.go @@ -30,33 +30,6 @@ const ( PROJECT_STATUS_DELETING = "DELETING" PROJECT_STATUS_INACTIVE = "INACTIVE" - // SKE - SKE_CLUSTERS_TAINT_EFFECT_NO_SCHED = "NoSchedule" - SKE_CLUSTERS_TAINT_EFFECT_PREF_NO_SCHED = "PreferNoSchedule" - SKE_CLUSTERS_TAINT_EFFECT_NO_EXEC = "NoExecute" - - SKE_CLUSTERS_CRI_DOCKER = "docker" - SKE_CLUSTERS_CRI_CONTAINERD = "containerd" - - SKE_PROJECT_STATUS_UNSPECIFIED = "STATE_UNSPECIFIED" - SKE_PROJECT_STATUS_CREATING = "STATE_CREATING" - SKE_PROJECT_STATUS_CREATED = "STATE_CREATED" - SKE_PROJECT_STATUS_DELETING = "STATE_DELETING" - SKE_PROJECT_STATUS_FAILED = "STATE_FAILED" - - SKE_CLUSTER_STATUS_CREATING = "STATE_CREATING" - SKE_CLUSTER_STATUS_DELETING = "STATE_DELETING" - SKE_CLUSTER_STATUS_HEALTHY = "STATE_HEALTHY" - SKE_CLUSTER_STATUS_HIBERNATED = "STATE_HIBERNATED" - SKE_CLUSTER_STATUS_HIBERNATING = "STATE_HIBERNATING" - SKE_CLUSTER_STATUS_RECONCILING = "STATE_RECONCILING" - SKE_CLUSTER_STATUS_UNHEALTHY = "STATE_UNHEALTHY" - SKE_CLUSTER_STATUS_UNSPECIFIED = "STATE_UNSPECIFIED" - SKE_CLUSTER_STATUS_WAKINGUP = "STATE_WAKINGUP" - - SKE_VERSION_STATE_DEPRECATED = "deprecated" - SKE_VERSION_STATE_SUPPORTED = "supported" - // granularity options; to be used for costs.GetProjectCosts() COSTS_GRANULARITY_NONE = "none" COSTS_GRANULARITY_DAILY = "daily" diff --git a/pkg/services/kubernetes/v1.0/generated/cluster/validate.go b/pkg/services/kubernetes/v1.0/generated/cluster/validate.go index 535619ef..531ed253 100644 --- a/pkg/services/kubernetes/v1.0/generated/cluster/validate.go +++ b/pkg/services/kubernetes/v1.0/generated/cluster/validate.go @@ -7,7 +7,6 @@ import ( "fmt" "regexp" - "github.com/SchwarzIT/community-stackit-go-client/pkg/consts" "github.com/SchwarzIT/community-stackit-go-client/pkg/validate" ) @@ -97,11 +96,11 @@ func ValidateNodePool(np Nodepool) error { // ValidateTaint validates a given node pool taint func ValidateTaint(t Taint) error { switch t.Effect { - case consts.SKE_CLUSTERS_TAINT_EFFECT_NO_EXEC: + case NO_EXECUTE: fallthrough - case consts.SKE_CLUSTERS_TAINT_EFFECT_NO_SCHED: + case NO_SCHEDULE: fallthrough - case consts.SKE_CLUSTERS_TAINT_EFFECT_PREF_NO_SCHED: + case PREFER_NO_SCHEDULE: default: return fmt.Errorf("invalid taint effect '%s'", t.Effect) } diff --git a/pkg/services/kubernetes/v1.0/include/cluster/validate.go b/pkg/services/kubernetes/v1.0/include/cluster/validate.go index 47d75de2..55422a64 100644 --- a/pkg/services/kubernetes/v1.0/include/cluster/validate.go +++ b/pkg/services/kubernetes/v1.0/include/cluster/validate.go @@ -7,7 +7,6 @@ import ( "fmt" "regexp" - "github.com/SchwarzIT/community-stackit-go-client/pkg/consts" "github.com/SchwarzIT/community-stackit-go-client/pkg/services/kubernetes/v1.0/generated/cluster" "github.com/SchwarzIT/community-stackit-go-client/pkg/validate" ) @@ -98,11 +97,11 @@ func ValidateNodePool(np cluster.Nodepool) error { // ValidateTaint validates a given node pool taint func ValidateTaint(t cluster.Taint) error { switch t.Effect { - case consts.SKE_CLUSTERS_TAINT_EFFECT_NO_EXEC: + case cluster.NO_EXECUTE: fallthrough - case consts.SKE_CLUSTERS_TAINT_EFFECT_NO_SCHED: + case cluster.NO_SCHEDULE: fallthrough - case consts.SKE_CLUSTERS_TAINT_EFFECT_PREF_NO_SCHED: + case cluster.PREFER_NO_SCHEDULE: default: return fmt.Errorf("invalid taint effect '%s'", t.Effect) } diff --git a/services_legacy.go b/services_legacy.go index ed5ee318..88789f10 100644 --- a/services_legacy.go +++ b/services_legacy.go @@ -4,7 +4,6 @@ import ( "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/argus" "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/costs" dataservices "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/data-services" - "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/kubernetes" mongodbFlex "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/mongodb-flex" objectstorage "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/object-storage" postgresFlex "github.com/SchwarzIT/community-stackit-go-client/pkg/api/v1/postgres-flex" @@ -19,7 +18,6 @@ type ProductiveServices struct { Argus *argus.ArgusService Costs *costs.CostsService DataServices DataServices - Kubernetes *kubernetes.KubernetesService Membership *membership.MembershipService MongoDBFlex *mongodbFlex.MongoDBService ObjectStorage *objectstorage.ObjectStorageService @@ -47,7 +45,6 @@ func (c *Client) initLegacyServices() *Client { // init productive services c.Argus = argus.New(c) c.Costs = costs.New(c) - c.Kubernetes = kubernetes.New(c) c.Membership = membership.New(c) c.MongoDBFlex = mongodbFlex.New(c) c.ObjectStorage = objectstorage.New(c)