diff --git a/charts/kubernetes/Chart.yaml b/charts/kubernetes/Chart.yaml index 31b62ca..4eebe91 100644 --- a/charts/kubernetes/Chart.yaml +++ b/charts/kubernetes/Chart.yaml @@ -4,8 +4,8 @@ description: A Helm chart for deploying Unikorn Kubernetes Service type: application -version: v0.2.56-rc1 -appVersion: v0.2.56-rc1 +version: v0.2.56-rc2 +appVersion: v0.2.56-rc2 icon: https://raw.githubusercontent.com/unikorn-cloud/assets/main/images/logos/dark-on-light/icon.png diff --git a/go.mod b/go.mod index 81147f6..e16e251 100644 --- a/go.mod +++ b/go.mod @@ -12,8 +12,8 @@ require ( github.com/oapi-codegen/runtime v1.1.1 github.com/prometheus/client_golang v1.20.5 github.com/spf13/pflag v1.0.5 - github.com/unikorn-cloud/core v0.1.89-rc3 - github.com/unikorn-cloud/identity v0.2.53-rc2 + github.com/unikorn-cloud/core v0.1.89-rc5 + github.com/unikorn-cloud/identity v0.2.53-rc4 github.com/unikorn-cloud/region v0.1.48-rc1 go.opentelemetry.io/otel v1.34.0 go.opentelemetry.io/otel/sdk v1.34.0 diff --git a/go.sum b/go.sum index 7d4f4da..0a78add 100644 --- a/go.sum +++ b/go.sum @@ -171,10 +171,10 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/unikorn-cloud/core v0.1.89-rc3 h1:SFsNCTfjQd9sawO7/6HFmNRacspfYG6SlVH8vF3Wy2U= -github.com/unikorn-cloud/core v0.1.89-rc3/go.mod h1:UW7g0AFLjY6r3KVPv9SPu/POttZx6Tl6UZ30+s4da+M= -github.com/unikorn-cloud/identity v0.2.53-rc2 h1:rkK0jsN5kOzKuq2vK4KdYezKMC7qwfq/0ahBm3jiEFs= -github.com/unikorn-cloud/identity v0.2.53-rc2/go.mod h1:DqOMDX52AYimYK1j/NkR9wu0kPqX4EvZDa5qn8EIIE0= +github.com/unikorn-cloud/core v0.1.89-rc5 h1:DYT9DOO9gdqzN3U1cirxWDgvECN9vfPuZda7KQE86KI= +github.com/unikorn-cloud/core v0.1.89-rc5/go.mod h1:UW7g0AFLjY6r3KVPv9SPu/POttZx6Tl6UZ30+s4da+M= +github.com/unikorn-cloud/identity v0.2.53-rc4 h1:TbVMI6JftmuF1EI3mWFL9Prr6Xtw1jsoJAsbzeaj8bY= +github.com/unikorn-cloud/identity v0.2.53-rc4/go.mod h1:z1Y9lXyjEovrjOp7FylZN/k6wGU3/e6i6s7YWmbaRrE= github.com/unikorn-cloud/region v0.1.48-rc1 h1:+AMe3AqUSQcPQ7qAwudrzwoVYe6RGUCpZnbbYqZm1bg= github.com/unikorn-cloud/region v0.1.48-rc1/go.mod h1:lRRDZnbJXCgpN37ylzfGnRWzsNJaR3WTpNyeh96SBpE= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= diff --git a/pkg/provisioners/managers/cluster/provisioner.go b/pkg/provisioners/managers/cluster/provisioner.go index b81b48b..88c57a0 100644 --- a/pkg/provisioners/managers/cluster/provisioner.go +++ b/pkg/provisioners/managers/cluster/provisioner.go @@ -567,7 +567,7 @@ func (p *Provisioner) Provision(ctx context.Context) error { } // Likewise identity creation is provisioned asynchronously as it too takes a - // long time, epspectially if a physical network is being provisioned and that + // long time, especially if a physical network is being provisioned and that // needs to go out and talk to swiches. clientContext, client, err := p.getRegionClient(ctx, "provision") if err != nil { diff --git a/pkg/server/handler/cluster/client.go b/pkg/server/handler/cluster/client.go index 7aba6fe..225c977 100644 --- a/pkg/server/handler/cluster/client.go +++ b/pkg/server/handler/cluster/client.go @@ -19,6 +19,8 @@ package cluster import ( "context" + goerrors "errors" + "fmt" "net" "net/http" "slices" @@ -30,17 +32,18 @@ import ( coreapi "github.com/unikorn-cloud/core/pkg/openapi" "github.com/unikorn-cloud/core/pkg/server/conversion" "github.com/unikorn-cloud/core/pkg/server/errors" + identityapi "github.com/unikorn-cloud/identity/pkg/openapi" unikornv1 "github.com/unikorn-cloud/kubernetes/pkg/apis/unikorn/v1alpha1" "github.com/unikorn-cloud/kubernetes/pkg/openapi" "github.com/unikorn-cloud/kubernetes/pkg/provisioners/helmapplications/clusteropenstack" "github.com/unikorn-cloud/kubernetes/pkg/provisioners/helmapplications/vcluster" "github.com/unikorn-cloud/kubernetes/pkg/server/handler/clustermanager" "github.com/unikorn-cloud/kubernetes/pkg/server/handler/common" + "github.com/unikorn-cloud/kubernetes/pkg/server/handler/region" regionapi "github.com/unikorn-cloud/region/pkg/openapi" corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" "k8s.io/utils/ptr" @@ -48,6 +51,12 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) +var ( + ErrConsistency = goerrors.New("consistency error") + + ErrAPI = goerrors.New("remote api error") +) + type Options struct { ControlPlaneCPUsMax int ControlPlaneMemoryMaxGiB int @@ -83,16 +92,20 @@ type Client struct { // options control various defaults and the like. options *Options + // identity is a client to access the identity service. + identity identityapi.ClientWithResponsesInterface + // region is a client to access regions. region regionapi.ClientWithResponsesInterface } // NewClient returns a new client with required parameters. -func NewClient(client client.Client, namespace string, options *Options, region regionapi.ClientWithResponsesInterface) *Client { +func NewClient(client client.Client, namespace string, options *Options, identity identityapi.ClientWithResponsesInterface, region regionapi.ClientWithResponsesInterface) *Client { return &Client{ client: client, namespace: namespace, options: options, + identity: identity, region: region, } } @@ -187,6 +200,129 @@ func (c *Client) GetKubeconfig(ctx context.Context, organizationID, projectID, c return secret.Data["value"], nil } +func (c *Client) generateAllocations(ctx context.Context, organizationID string, resource *unikornv1.KubernetesCluster) (*identityapi.AllocationWrite, error) { + flavors, err := region.Flavors(ctx, c.region, organizationID, resource.Spec.RegionID) + if err != nil { + return nil, err + } + + var serversCommitted int + + var serversReserved int + + var gpusCommitted int + + var gpusReserved int + + // NOTE: the control plane is "free". + for _, pool := range resource.Spec.WorkloadPools.Pools { + serversMinimum := *pool.Replicas + serversMaximum := *pool.Replicas + + if pool.Autoscaling != nil { + serversMinimum = *pool.Autoscaling.MinimumReplicas + serversMaximum = *pool.Autoscaling.MaximumReplicas + } + + serversCommitted += serversMinimum + serversReserved += serversMaximum - serversMinimum + + flavorByID := func(f regionapi.Flavor) bool { + return f.Metadata.Id == *pool.FlavorID + } + + index := slices.IndexFunc(flavors, flavorByID) + if index < 0 { + return nil, fmt.Errorf("%w: flavorID does not exist", ErrConsistency) + } + + flavor := flavors[index] + + if flavor.Spec.Gpu != nil { + gpusCommitted += serversMinimum * flavor.Spec.Gpu.PhysicalCount + gpusReserved += serversMaximum * flavor.Spec.Gpu.PhysicalCount + } + } + + request := &identityapi.AllocationWrite{ + Metadata: coreapi.ResourceWriteMetadata{ + Name: "unused", + }, + Spec: identityapi.AllocationSpec{ + Kind: "kubernetescluster", + Id: resource.Name, + Allocations: identityapi.QuotaListDetailed{ + { + Kind: "clusters", + Committed: 1, + Reserved: 0, + }, + { + Kind: "servers", + Committed: serversCommitted, + Reserved: serversReserved, + }, + { + Kind: "gpus", + Committed: gpusCommitted, + Reserved: gpusReserved, + }, + }, + }, + } + + return request, nil +} + +func (c *Client) createAllocation(ctx context.Context, organizationID, projectID string, resource *unikornv1.KubernetesCluster) (*identityapi.AllocationRead, error) { + allocations, err := c.generateAllocations(ctx, organizationID, resource) + if err != nil { + return nil, err + } + + resp, err := c.identity.PostApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsWithResponse(ctx, organizationID, projectID, *allocations) + if err != nil { + return nil, err + } + + if resp.StatusCode() != http.StatusCreated { + return nil, fmt.Errorf("%w: unexpected status code %d", ErrAPI, resp.StatusCode()) + } + + return resp.JSON201, nil +} + +func (c *Client) updateAllocation(ctx context.Context, organizationID, projectID string, resource *unikornv1.KubernetesCluster) error { + allocations, err := c.generateAllocations(ctx, organizationID, resource) + if err != nil { + return err + } + + resp, err := c.identity.PutApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDWithResponse(ctx, organizationID, projectID, resource.Annotations[constants.AllocationAnnotation], *allocations) + if err != nil { + return err + } + + if resp.StatusCode() != http.StatusOK { + return fmt.Errorf("%w: unexpected status code %d", ErrAPI, resp.StatusCode()) + } + + return nil +} + +func (c *Client) deleteAllocation(ctx context.Context, organizationID, projectID, allocationID string) error { + resp, err := c.identity.DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDAllocationsAllocationIDWithResponse(ctx, organizationID, projectID, allocationID) + if err != nil { + return err + } + + if resp.StatusCode() != http.StatusAccepted { + return fmt.Errorf("%w: unexpected status code %d", ErrAPI, resp.StatusCode()) + } + + return nil +} + func (c *Client) createIdentity(ctx context.Context, organizationID, projectID, regionID, clusterID string) (*regionapi.IdentityRead, error) { tags := coreapi.TagList{ coreapi.Tag{ @@ -280,12 +416,13 @@ func (c *Client) getRegion(ctx context.Context, organizationID, regionID string) return &results[index], nil } -func (c *Client) applyCloudSpecificConfiguration(ctx context.Context, organizationID, projectID, regionID string, identity *regionapi.IdentityRead, cluster *unikornv1.KubernetesCluster) error { +func (c *Client) applyCloudSpecificConfiguration(ctx context.Context, organizationID, projectID, regionID string, allocation *identityapi.AllocationRead, identity *regionapi.IdentityRead, cluster *unikornv1.KubernetesCluster) error { // Save the identity ID for later cleanup. if cluster.Annotations == nil { cluster.Annotations = map[string]string{} } + cluster.Annotations[constants.AllocationAnnotation] = allocation.Metadata.Id cluster.Annotations[constants.IdentityAnnotation] = identity.Metadata.Id // Apply any region specific configuration based on feature flags. @@ -311,6 +448,31 @@ func (c *Client) applyCloudSpecificConfiguration(ctx context.Context, organizati return nil } +func preserveAnnotations(requested, current *unikornv1.KubernetesCluster) error { + identity, ok := current.Annotations[constants.IdentityAnnotation] + if !ok { + return fmt.Errorf("%w: identity annotation missing", ErrConsistency) + } + + allocation, ok := current.Annotations[constants.AllocationAnnotation] + if !ok { + return fmt.Errorf("%w: allocation annotation missing", ErrConsistency) + } + + if requested.Annotations == nil { + requested.Annotations = map[string]string{} + } + + requested.Annotations[constants.IdentityAnnotation] = identity + requested.Annotations[constants.AllocationAnnotation] = allocation + + if network, ok := current.Annotations[constants.PhysicalNetworkAnnotation]; ok { + requested.Annotations[constants.PhysicalNetworkAnnotation] = network + } + + return nil +} + // Create creates the implicit cluster indentified by the JTW claims. func (c *Client) Create(ctx context.Context, organizationID, projectID string, request *openapi.KubernetesClusterWrite) (*openapi.KubernetesClusterRead, error) { namespace, err := common.New(c.client).ProjectNamespace(ctx, organizationID, projectID) @@ -333,12 +495,17 @@ func (c *Client) Create(ctx context.Context, organizationID, projectID string, r return nil, err } + allocation, err := c.createAllocation(ctx, organizationID, projectID, cluster) + if err != nil { + return nil, errors.OAuth2ServerError("failed to create quota allocation").WithError(err) + } + identity, err := c.createIdentity(ctx, organizationID, projectID, request.Spec.RegionId, cluster.Name) if err != nil { return nil, err } - if err := c.applyCloudSpecificConfiguration(ctx, organizationID, projectID, request.Spec.RegionId, identity, cluster); err != nil { + if err := c.applyCloudSpecificConfiguration(ctx, organizationID, projectID, request.Spec.RegionId, allocation, identity, cluster); err != nil { return nil, err } @@ -356,15 +523,13 @@ func (c *Client) Delete(ctx context.Context, organizationID, projectID, clusterI return err } - if namespace.DeletionTimestamp != nil { - return errors.OAuth2InvalidRequest("control plane is being deleted") - } + cluster, err := c.get(ctx, namespace.Name, clusterID) + if err != nil { + if kerrors.IsNotFound(err) { + return errors.HTTPNotFound().WithError(err) + } - cluster := &unikornv1.KubernetesCluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: clusterID, - Namespace: namespace.Name, - }, + return errors.OAuth2ServerError("failed to get cluster") } if err := c.client.Delete(ctx, cluster); err != nil { @@ -375,6 +540,10 @@ func (c *Client) Delete(ctx context.Context, organizationID, projectID, clusterI return errors.OAuth2ServerError("failed to delete cluster").WithError(err) } + if err := c.deleteAllocation(ctx, organizationID, projectID, cluster.Annotations[constants.AllocationAnnotation]); err != nil { + return errors.OAuth2ServerError("failed to delete quota allocation").WithError(err) + } + return nil } @@ -403,6 +572,14 @@ func (c *Client) Update(ctx context.Context, organizationID, projectID, clusterI return errors.OAuth2ServerError("failed to merge metadata").WithError(err) } + if err := preserveAnnotations(required, current); err != nil { + return errors.OAuth2ServerError("failed to merge annotations").WithError(err) + } + + if err := c.updateAllocation(ctx, organizationID, projectID, required); err != nil { + return errors.OAuth2ServerError("failed to update quota allocation").WithError(err) + } + // Experience has taught me that modifying caches by accident is a bad thing // so be extra safe and deep copy the existing resource. updated := current.DeepCopy() diff --git a/pkg/server/handler/handler.go b/pkg/server/handler/handler.go index 24e2a5a..b805c58 100644 --- a/pkg/server/handler/handler.go +++ b/pkg/server/handler/handler.go @@ -52,22 +52,40 @@ type Handler struct { // have to be granted unnecessary privilige. issuer *identityclient.TokenIssuer + // identity is a client to access the identity service. + identity *identityclient.Client + // region is a client to access regions. region *regionclient.Client } -func New(client client.Client, namespace string, options *Options, issuer *identityclient.TokenIssuer, region *regionclient.Client) (*Handler, error) { +func New(client client.Client, namespace string, options *Options, issuer *identityclient.TokenIssuer, identity *identityclient.Client, region *regionclient.Client) (*Handler, error) { h := &Handler{ client: client, namespace: namespace, options: options, issuer: issuer, + identity: identity, region: region, } return h, nil } +func (h *Handler) identityClient(ctx context.Context) (*identityapi.ClientWithResponses, error) { + token, err := h.issuer.Issue(ctx, "kubernetes-api") + if err != nil { + return nil, err + } + + identity, err := h.identity.Client(ctx, token) + if err != nil { + return nil, err + } + + return identity, nil +} + func (h *Handler) regionClient(ctx context.Context) (*regionapi.ClientWithResponses, error) { token, err := h.issuer.Issue(ctx, "kubernetes-api") if err != nil { @@ -94,18 +112,20 @@ func (h *Handler) setUncacheable(w http.ResponseWriter) { } func (h *Handler) GetApiV1OrganizationsOrganizationIDRegionsRegionIDFlavors(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, regionID openapi.RegionIDParameter) { - if err := rbac.AllowOrganizationScope(r.Context(), "kubernetes:flavors", identityapi.Read, organizationID); err != nil { + ctx := r.Context() + + if err := rbac.AllowOrganizationScope(ctx, "kubernetes:flavors", identityapi.Read, organizationID); err != nil { errors.HandleError(w, r, err) return } - client, err := h.regionClient(r.Context()) + client, err := h.regionClient(ctx) if err != nil { errors.HandleError(w, r, err) return } - result, err := region.Flavors(r.Context(), client, organizationID, regionID) + result, err := region.Flavors(ctx, client, organizationID, regionID) if err != nil { errors.HandleError(w, r, errors.OAuth2ServerError("unable to read flavors").WithError(err)) return @@ -115,18 +135,20 @@ func (h *Handler) GetApiV1OrganizationsOrganizationIDRegionsRegionIDFlavors(w ht } func (h *Handler) GetApiV1OrganizationsOrganizationIDRegionsRegionIDImages(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, regionID openapi.RegionIDParameter) { - if err := rbac.AllowOrganizationScope(r.Context(), "kubernetes:images", identityapi.Read, organizationID); err != nil { + ctx := r.Context() + + if err := rbac.AllowOrganizationScope(ctx, "kubernetes:images", identityapi.Read, organizationID); err != nil { errors.HandleError(w, r, err) return } - client, err := h.regionClient(r.Context()) + client, err := h.regionClient(ctx) if err != nil { errors.HandleError(w, r, err) return } - result, err := region.Images(r.Context(), client, organizationID, regionID) + result, err := region.Images(ctx, client, organizationID, regionID) if err != nil { errors.HandleError(w, r, errors.OAuth2ServerError("unable to read flavors").WithError(err)) return @@ -136,14 +158,14 @@ func (h *Handler) GetApiV1OrganizationsOrganizationIDRegionsRegionIDImages(w htt } func (h *Handler) GetApiV1OrganizationsOrganizationIDClustermanagers(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter) { - result, err := clustermanager.NewClient(h.client).List(r.Context(), organizationID) + ctx := r.Context() + + result, err := clustermanager.NewClient(h.client).List(ctx, organizationID) if err != nil { errors.HandleError(w, r, err) return } - ctx := r.Context() - result = slices.DeleteFunc(result, func(resource openapi.ClusterManagerRead) bool { return rbac.AllowProjectScope(ctx, "kubernetes:clustermanagers", identityapi.Read, organizationID, resource.Metadata.ProjectId) != nil }) @@ -153,7 +175,9 @@ func (h *Handler) GetApiV1OrganizationsOrganizationIDClustermanagers(w http.Resp } func (h *Handler) PostApiV1OrganizationsOrganizationIDProjectsProjectIDClustermanagers(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, projectID openapi.ProjectIDParameter) { - if err := rbac.AllowProjectScope(r.Context(), "kubernetes:clustermanagers", identityapi.Create, organizationID, projectID); err != nil { + ctx := r.Context() + + if err := rbac.AllowProjectScope(ctx, "kubernetes:clustermanagers", identityapi.Create, organizationID, projectID); err != nil { errors.HandleError(w, r, err) return } @@ -165,7 +189,7 @@ func (h *Handler) PostApiV1OrganizationsOrganizationIDProjectsProjectIDClusterma return } - result, err := clustermanager.NewClient(h.client).Create(r.Context(), organizationID, projectID, request) + result, err := clustermanager.NewClient(h.client).Create(ctx, organizationID, projectID, request) if err != nil { errors.HandleError(w, r, err) return @@ -176,12 +200,14 @@ func (h *Handler) PostApiV1OrganizationsOrganizationIDProjectsProjectIDClusterma } func (h *Handler) DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDClustermanagersClusterManagerID(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, projectID openapi.ProjectIDParameter, clusterManagerID openapi.ClusterManagerIDParameter) { - if err := rbac.AllowProjectScope(r.Context(), "kubernetes:clustermanagers", identityapi.Delete, organizationID, projectID); err != nil { + ctx := r.Context() + + if err := rbac.AllowProjectScope(ctx, "kubernetes:clustermanagers", identityapi.Delete, organizationID, projectID); err != nil { errors.HandleError(w, r, err) return } - if err := clustermanager.NewClient(h.client).Delete(r.Context(), organizationID, projectID, clusterManagerID); err != nil { + if err := clustermanager.NewClient(h.client).Delete(ctx, organizationID, projectID, clusterManagerID); err != nil { errors.HandleError(w, r, err) return } @@ -191,7 +217,9 @@ func (h *Handler) DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDCluster } func (h *Handler) PutApiV1OrganizationsOrganizationIDProjectsProjectIDClustermanagersClusterManagerID(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, projectID openapi.ProjectIDParameter, clusterManagerID openapi.ClusterManagerIDParameter) { - if err := rbac.AllowProjectScope(r.Context(), "kubernetes:clustermanagers", identityapi.Update, organizationID, projectID); err != nil { + ctx := r.Context() + + if err := rbac.AllowProjectScope(ctx, "kubernetes:clustermanagers", identityapi.Update, organizationID, projectID); err != nil { errors.HandleError(w, r, err) return } @@ -203,7 +231,7 @@ func (h *Handler) PutApiV1OrganizationsOrganizationIDProjectsProjectIDClusterman return } - if err := clustermanager.NewClient(h.client).Update(r.Context(), organizationID, projectID, clusterManagerID, request); err != nil { + if err := clustermanager.NewClient(h.client).Update(ctx, organizationID, projectID, clusterManagerID, request); err != nil { errors.HandleError(w, r, err) return } @@ -212,21 +240,35 @@ func (h *Handler) PutApiV1OrganizationsOrganizationIDProjectsProjectIDClusterman w.WriteHeader(http.StatusAccepted) } +func (h *Handler) clusterClient(ctx context.Context) (*cluster.Client, error) { + identity, err := h.identityClient(ctx) + if err != nil { + return nil, err + } + + region, err := h.regionClient(ctx) + if err != nil { + return nil, err + } + + return cluster.NewClient(h.client, h.namespace, &h.options.Cluster, identity, region), nil +} + func (h *Handler) GetApiV1OrganizationsOrganizationIDClusters(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter) { - region, err := h.regionClient(r.Context()) + ctx := r.Context() + + clusters, err := h.clusterClient(ctx) if err != nil { errors.HandleError(w, r, err) return } - result, err := cluster.NewClient(h.client, h.namespace, &h.options.Cluster, region).List(r.Context(), organizationID) + result, err := clusters.List(ctx, organizationID) if err != nil { errors.HandleError(w, r, err) return } - ctx := r.Context() - result = slices.DeleteFunc(result, func(resource openapi.KubernetesClusterRead) bool { return rbac.AllowProjectScope(ctx, "kubernetes:clusters", identityapi.Read, organizationID, resource.Metadata.ProjectId) != nil }) @@ -236,7 +278,9 @@ func (h *Handler) GetApiV1OrganizationsOrganizationIDClusters(w http.ResponseWri } func (h *Handler) PostApiV1OrganizationsOrganizationIDProjectsProjectIDClusters(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, projectID openapi.ProjectIDParameter) { - if err := rbac.AllowProjectScope(r.Context(), "kubernetes:clusters", identityapi.Create, organizationID, projectID); err != nil { + ctx := r.Context() + + if err := rbac.AllowProjectScope(ctx, "kubernetes:clusters", identityapi.Create, organizationID, projectID); err != nil { errors.HandleError(w, r, err) return } @@ -248,13 +292,13 @@ func (h *Handler) PostApiV1OrganizationsOrganizationIDProjectsProjectIDClusters( return } - region, err := h.regionClient(r.Context()) + clusters, err := h.clusterClient(ctx) if err != nil { errors.HandleError(w, r, err) return } - result, err := cluster.NewClient(h.client, h.namespace, &h.options.Cluster, region).Create(r.Context(), organizationID, projectID, request) + result, err := clusters.Create(ctx, organizationID, projectID, request) if err != nil { errors.HandleError(w, r, err) return @@ -265,18 +309,20 @@ func (h *Handler) PostApiV1OrganizationsOrganizationIDProjectsProjectIDClusters( } func (h *Handler) DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDClustersClusterID(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, projectID openapi.ProjectIDParameter, clusterID openapi.ClusterIDParameter) { - if err := rbac.AllowProjectScope(r.Context(), "kubernetes:clusters", identityapi.Delete, organizationID, projectID); err != nil { + ctx := r.Context() + + if err := rbac.AllowProjectScope(ctx, "kubernetes:clusters", identityapi.Delete, organizationID, projectID); err != nil { errors.HandleError(w, r, err) return } - region, err := h.regionClient(r.Context()) + clusters, err := h.clusterClient(ctx) if err != nil { errors.HandleError(w, r, err) return } - if err := cluster.NewClient(h.client, h.namespace, &h.options.Cluster, region).Delete(r.Context(), organizationID, projectID, clusterID); err != nil { + if err := clusters.Delete(ctx, organizationID, projectID, clusterID); err != nil { errors.HandleError(w, r, err) return } @@ -286,7 +332,9 @@ func (h *Handler) DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDCluster } func (h *Handler) PutApiV1OrganizationsOrganizationIDProjectsProjectIDClustersClusterID(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, projectID openapi.ProjectIDParameter, clusterID openapi.ClusterIDParameter) { - if err := rbac.AllowProjectScope(r.Context(), "kubernetes:clusters", identityapi.Update, organizationID, projectID); err != nil { + ctx := r.Context() + + if err := rbac.AllowProjectScope(ctx, "kubernetes:clusters", identityapi.Update, organizationID, projectID); err != nil { errors.HandleError(w, r, err) return } @@ -298,13 +346,13 @@ func (h *Handler) PutApiV1OrganizationsOrganizationIDProjectsProjectIDClustersCl return } - region, err := h.regionClient(r.Context()) + clusters, err := h.clusterClient(ctx) if err != nil { errors.HandleError(w, r, err) return } - if err := cluster.NewClient(h.client, h.namespace, &h.options.Cluster, region).Update(r.Context(), organizationID, projectID, clusterID, request); err != nil { + if err := clusters.Update(ctx, organizationID, projectID, clusterID, request); err != nil { errors.HandleError(w, r, err) return } @@ -314,18 +362,20 @@ func (h *Handler) PutApiV1OrganizationsOrganizationIDProjectsProjectIDClustersCl } func (h *Handler) GetApiV1OrganizationsOrganizationIDProjectsProjectIDClustersClusterIDKubeconfig(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, projectID openapi.ProjectIDParameter, clusterID openapi.ClusterIDParameter) { - if err := rbac.AllowProjectScope(r.Context(), "kubernetes:clusters", identityapi.Read, organizationID, projectID); err != nil { + ctx := r.Context() + + if err := rbac.AllowProjectScope(ctx, "kubernetes:clusters", identityapi.Read, organizationID, projectID); err != nil { errors.HandleError(w, r, err) return } - region, err := h.regionClient(r.Context()) + clusters, err := h.clusterClient(ctx) if err != nil { errors.HandleError(w, r, err) return } - result, err := cluster.NewClient(h.client, h.namespace, &h.options.Cluster, region).GetKubeconfig(r.Context(), organizationID, projectID, clusterID) + result, err := clusters.GetKubeconfig(ctx, organizationID, projectID, clusterID) if err != nil { errors.HandleError(w, r, err) return diff --git a/pkg/server/server.go b/pkg/server/server.go index 4844e58..c4d5be7 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -157,9 +157,10 @@ func (s *Server) GetServer(client client.Client) (*http.Server, error) { // prevent the user having to be granted excessive privilege. issuer := identityclient.NewTokenIssuer(client, s.IdentityOptions, &s.ClientOptions, constants.Application, constants.Version) + identity := identityclient.New(client, s.IdentityOptions, &s.ClientOptions) region := regionclient.New(client, s.RegionOptions, &s.ClientOptions) - handlerInterface, err := handler.New(client, s.Options.Namespace, &s.HandlerOptions, issuer, region) + handlerInterface, err := handler.New(client, s.Options.Namespace, &s.HandlerOptions, issuer, identity, region) if err != nil { return nil, err }