From 537028cbac582d5e313ebb97f244b6c2887cf8d0 Mon Sep 17 00:00:00 2001 From: Simon Murray Date: Wed, 17 Jul 2024 09:09:58 +0100 Subject: [PATCH] Update Creation Auditing To actually audit resource creation meaningfully, we need to get the resource ID, and the only way to do that in a generic way is to return the generated resource in all POST APIs. While in the area, there was a backlog item to clean up cluster generation as passing around a million arguments sucked, so package these up as an object. --- charts/kubernetes/Chart.yaml | 4 +- go.mod | 6 +- go.sum | 12 +- pkg/openapi/client.go | 16 ++ pkg/openapi/schema.go | 199 ++++++++++---------- pkg/openapi/server.spec.yaml | 29 ++- pkg/openapi/types.go | 6 + pkg/server/handler/cluster/client.go | 48 +++-- pkg/server/handler/cluster/conversion.go | 101 ++++++---- pkg/server/handler/clustermanager/client.go | 13 +- pkg/server/handler/handler.go | 10 +- 11 files changed, 264 insertions(+), 180 deletions(-) diff --git a/charts/kubernetes/Chart.yaml b/charts/kubernetes/Chart.yaml index e40d2274..94b5f427 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.28 -appVersion: v0.2.28 +version: v0.2.29 +appVersion: v0.2.29 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 5ae23962..84c32d37 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,9 @@ require ( github.com/prometheus/client_golang v1.19.1 github.com/spdx/tools-golang v0.5.5 github.com/spf13/pflag v1.0.5 - github.com/unikorn-cloud/core v0.1.59 - github.com/unikorn-cloud/identity v0.2.25 - github.com/unikorn-cloud/region v0.1.20 + github.com/unikorn-cloud/core v0.1.62 + github.com/unikorn-cloud/identity v0.2.28 + github.com/unikorn-cloud/region v0.1.22 go.opentelemetry.io/otel v1.28.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 go.opentelemetry.io/otel/sdk v1.28.0 diff --git a/go.sum b/go.sum index 1530d1ba..b54c6035 100644 --- a/go.sum +++ b/go.sum @@ -181,12 +181,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.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.59 h1:Fow+RmWADvIHcDGnKxeE+m7uJzq2ARJb1/nPA2tY6+o= -github.com/unikorn-cloud/core v0.1.59/go.mod h1:Cd0zU1LrKo+OwnnCwuTQ+QL3yibnkjDHtkujfDM4AdE= -github.com/unikorn-cloud/identity v0.2.25 h1:nb45gnI8o/12idVodWtAVO5WRFW8cTg6PFQHYjLd3DM= -github.com/unikorn-cloud/identity v0.2.25/go.mod h1:8WxgWetyrCvLjRGAeEWy5hWXDJ8prVz+FNt9sG17lV8= -github.com/unikorn-cloud/region v0.1.20 h1:SblfR/TO7gnROiz2nbxY4YLMmHEbdigj2wpVYhQENIY= -github.com/unikorn-cloud/region v0.1.20/go.mod h1:D63xJbCu+yNrjhPUT3h7JBWoWta64qpG+z5tNnemVLU= +github.com/unikorn-cloud/core v0.1.62 h1:EbXZxQxBIYjWC/LVLw8xAd46609u5GLc2DxnrZnLYxE= +github.com/unikorn-cloud/core v0.1.62/go.mod h1:Cd0zU1LrKo+OwnnCwuTQ+QL3yibnkjDHtkujfDM4AdE= +github.com/unikorn-cloud/identity v0.2.28 h1:tJqkcTurte0oEHUW6EUjcZ69x2z0KI8puHSBKHF6ECE= +github.com/unikorn-cloud/identity v0.2.28/go.mod h1:k3Dfr604vlP5ryJJsFc412vgaIuztCzxsuiQwa3ws1Q= +github.com/unikorn-cloud/region v0.1.22 h1:+4CgMNnHmzwX8RjBa+nxh69o/mj+y49RRfm3/zwThYE= +github.com/unikorn-cloud/region v0.1.22/go.mod h1:cPRxUJ8rjry5zJzyQnuOmYZLdgCqTRvKeQPzAxiI548= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/pkg/openapi/client.go b/pkg/openapi/client.go index 86f1b341..d3b1555c 100644 --- a/pkg/openapi/client.go +++ b/pkg/openapi/client.go @@ -937,6 +937,7 @@ func (r GetApiV1OrganizationsOrganizationIDClustersResponse) StatusCode() int { type PostApiV1OrganizationsOrganizationIDProjectsProjectIDClustermanagersResponse struct { Body []byte HTTPResponse *http.Response + JSON202 *ClusterManagerResponse JSON400 *externalRef0.BadRequestResponse JSON401 *externalRef0.UnauthorizedResponse JSON403 *externalRef0.ForbiddenResponse @@ -1015,6 +1016,7 @@ func (r PutApiV1OrganizationsOrganizationIDProjectsProjectIDClustermanagersClust type PostApiV1OrganizationsOrganizationIDProjectsProjectIDClustersResponse struct { Body []byte HTTPResponse *http.Response + JSON202 *KubernetesClusterResponse JSON400 *externalRef0.BadRequestResponse JSON401 *externalRef0.UnauthorizedResponse JSON403 *externalRef0.ForbiddenResponse @@ -1421,6 +1423,13 @@ func ParsePostApiV1OrganizationsOrganizationIDProjectsProjectIDClustermanagersRe } switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest ClusterManagerResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON202 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest externalRef0.BadRequestResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -1583,6 +1592,13 @@ func ParsePostApiV1OrganizationsOrganizationIDProjectsProjectIDClustersResponse( } switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 202: + var dest KubernetesClusterResponse + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON202 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: var dest externalRef0.BadRequestResponse if err := json.Unmarshal(bodyBytes, &dest); err != nil { diff --git a/pkg/openapi/schema.go b/pkg/openapi/schema.go index f58091d8..7a1774cf 100644 --- a/pkg/openapi/schema.go +++ b/pkg/openapi/schema.go @@ -19,105 +19,106 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "eSLnxsPnh/8LAAD///B346h1bAAA", + "H4sIAAAAAAAC/+xdfXPaOtb/Kho/O3N3Z4HwmjaZ2dmhIe3NLjZJcdpNb/p0hH0AgS15LTlgOvnuz0iy", + "jTGGkJfe7d0nf5WAXo6OzsvvHB2p3w2H+QGjQAU3Tr8bAQ6xDwJC9ZfjRVxAeNG7TL+W37rAnZAEgjBq", + "nBr2FFDSDlHsQw2ZERdoBAijO+wRF/WsIXIYFZhQQieIUS9GHltAiBzMATlTHGJHTlm5pTTyRxByxEI0", + "jYMpUF5BXOBQIExdBNRFCyKmCK97yaa6V0W1kRML5DMubulxKzc6IhR5QCdiWjMqBpG0B1hMjYohyTZO", + "16s1KkYI/45ICK5xKsIIKgZ3puBjufo/hTA2To3/OVoz7kj/yo/m0QhCCgK4hX1YM+3+vpKObmKKJ49g", + "qa/bK9ZWEBkjUfKjy4AjygSCJeGiIttQRATycYxGcEuJH3jEIcKLkRMCFuBW0JiFCJbYDzy5U+mIhKct", + "EJ5gQrnI/ZhMd0vFFIvCpH/4bc825ofsPgsnmJIVljv84N7nG2udKqd8c9AfQncQshk44kGSk3b7qM2G", + "+gGE3ushgYt3zCWgjZeS4zNGRci8Sw9T+KibqB8ZFUDVRxxI5VBMPJpxuaLvRqIY8qMPArtYKOKSlbgw", + "xpEnFIMOo3xTxj6HRICmepOTCbEokNSiZEVobZNrW6yThkWt858Zb870XC+12IR0Q641AEf+EsJESpxr", + "nBqjeudk1ILj6gmGTrXdHL2pnrRH7eq43RyP3uDjEQYwKsYdhFwv8a5Ra76pNY2KsWDh3GPYvWTM48bp", + "b98NHAnGHewROlHEEEr8yP8IimRunNbvK4aPnSmhitixh+9YqMhw3nSO30LTrY5P8Kja7rTc6glu4Wqn", + "0XrTGb95224ejxTv0qFa95Wt3fx6+H7Oi9zeuaXrfcnM6EH7qiSaB4xyLc3YcSAQ4H5MvixXw3ToKeZo", + "BEBR2k3ZyAXxPGkox5E3Jp4nv+UxdaYhoyziXly7pTcsUl4jYJ6nXE0InEWhA2oAn1EiWIiI4NI8i4gr", + "NyIZ5IEkoybFJCdjeWoPlcLfNsVQiTdh1CZqt5r1Zqtaf1NtNexG/bTdOW13vhhFpl+G7I64wBGmCHsC", + "QooFuZOL0fOCi7hgIZ4owyWbhkj7NMJFSEaR3K60BXZCxrl0eoC2d7OG0HvAIgqBoymZTKv4DhMPj4gn", + "4goi1AnBByqwhzjFAZ8ywbW/ws48CqTvcwnHiVw47A7CWDs0PsUhuGhMPEA+i6jg6M8hYPdoIUVN+uL4", + "L8rSPkL+E4n3GJ1MWUgNZd/viFRNQidDtaPGqRHROWULmld5lzmRWkfC4akQAT89OkqHqhF2ZFSMaeRj", + "+hGwi0ceWHq2/no24ujt+dVqfonfBV96dWJ/eN/58q9/jM3hxeTLh/f1m2Ejuvnc8C6H/zBv/uV5Duku", + "L8i79ujzMnJWdYJ//Vh3euyu33JbbtxpmXHnzvGdO3PWXZhnJyvXd8jFr1+CL/9yz0atycnFrDsxz7rL", + "gX0VmbPrpmnPJ6Z93enPuu2BfR5fzNpv3Q9effTh+q/4s3U3mi3u0r8vf303dT9MJl98j496dXKx+uSb", + "s4v6jaRV0m7PW/3ZeTzonfNBrxtZs4vm4PP50jxrL8zenJt2NzJ73U6/1+Xm2WLZt8+jgX3d7g/by4Ft", + "rix/IaxhOx70zI51Vl/2Z92G1Zuv+r2ryLKv2pY95+bMiQb2ZGXan6aDYbtjzq7iwXDR6c/msdW7WI99", + "1l6as3l7ID/PbhZW76qDe9eRaV80b+x5NLDnHStW/ToD25F9Fv3eOe/PzpvmqtuWtFmrectcfeHWsL0Y", + "2JOlNazHVtzumL2bullfdAby+97Nst+bLPqzq5W5uq5f2eeL/qy7GPTmcb+X/5zQ1Svh0SdG+qv2W+fD", + "+zo+e+fjz0t+ObyYWZ9vYnP2cXpB3s0vh/+wTNtZ9Wc3Hcu+4eb5JDbP2g1r1m2Z1+fyc9OcnS+s4SL/", + "eZHMu+j3LhZ9ud+9m9an2flqcNZumLNJ3fqc60sW+c9p33SephXnPtcnS2tlRtZs3rD8bAxuztSaltvz", + "Xjf6dp6G9ecr9f1NbK5pT/p2+caa3wfCjNt1y77mVu88suzJsm9fRJbdlbxu3SS8N3s3qayt1zGst/qz", + "+cqyr+v93iQyV9cLy56aUh76s27dsq8a/Z7TkDJnfjaFHMeK2wur122Zw7ocq21JnelNlmbvRv6+tIiU", + "sfOW1VwIi7RXll7Dyjprty272xicK74szNlNQ/OhG1uz60zWBvZc8k/SuDRnk2hg3zTN2SfWt1M5TfrY", + "k1bWX33O9EfKb2vQu471525j0HtvWmqsq7q1uubWSo41b1n2lPftq2V/drUw7Zu4b08ic3bTvNrLs8Vy", + "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", + "/v8CAAD//5M2wcUdcQAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/pkg/openapi/server.spec.yaml b/pkg/openapi/server.spec.yaml index e1d4a29a..c15a1184 100644 --- a/pkg/openapi/server.spec.yaml +++ b/pkg/openapi/server.spec.yaml @@ -74,7 +74,7 @@ paths: $ref: '#/components/requestBodies/createControlPlaneRequest' responses: '202': - $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/acceptedResponse' + $ref: '#/components/responses/clusterManagerResponse' '400': $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/badRequestResponse' '401': @@ -175,7 +175,7 @@ paths: $ref: '#/components/requestBodies/createKubernetesClusterRequest' responses: '202': - $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/acceptedResponse' + $ref: '#/components/responses/kubernetesClusterResponse' '400': $ref: 'https://raw.githubusercontent.com/unikorn-cloud/core/main/pkg/openapi/common.spec.yaml#/components/responses/badRequestResponse' '401': @@ -643,6 +643,31 @@ components: description: A Kubernetes cluster configuration. content: application/octet-stream: {} + kubernetesClusterResponse: + description: A Kubernetes cluster. + content: + application/json: + schema: + $ref: '#/components/schemas/kubernetesClusterRead' + example: + metadata: + id: c7568e2d-f9ab-453d-9a3a-51375f78426b + name: cluster + organizationId: d4600d6e-e965-4b44-a808-84fb2fa36702 + projectId: cae219d7-10e5-4601-8c2c-ee7e066b93ce + creationTime: 2023-07-31T10:45:45Z + provisioningStatus: provisioned + spec: + regionId: b059b3e6-9ae5-42b7-94b4-f42fb7a6baee + version: v1.27.2 + clusterManagerId: b85525fc-40ca-482b-ac07-eca8b01b09c2 + workloadPools: + - name: default + machine: + disk: + size: 50 + flavorId: c7568e2d-f9ab-453d-9a3a-51375f78426b + replicas: 3 kubernetesClustersResponse: description: A list of Kubernetes clusters. content: diff --git a/pkg/openapi/types.go b/pkg/openapi/types.go index 5ecb7b75..5bd90850 100644 --- a/pkg/openapi/types.go +++ b/pkg/openapi/types.go @@ -186,9 +186,15 @@ type ProjectIDParameter = KubernetesNameParameter // ApplicationResponse A list of appications. type ApplicationResponse = Applications +// ClusterManagerResponse A cluster manager. +type ClusterManagerResponse = ClusterManagerRead + // ClusterManagersResponse A list of cluster managers. type ClusterManagersResponse = ClusterManagers +// KubernetesClusterResponse Kubernetes cluster read. +type KubernetesClusterResponse = KubernetesClusterRead + // KubernetesClustersResponse A list of Kubernetes clusters. type KubernetesClustersResponse = KubernetesClusters diff --git a/pkg/server/handler/cluster/client.go b/pkg/server/handler/cluster/client.go index 028746fa..4a1ae118 100644 --- a/pkg/server/handler/cluster/client.go +++ b/pkg/server/handler/cluster/client.go @@ -115,7 +115,7 @@ func (c *Client) List(ctx context.Context, organizationID string) (openapi.Kuber slices.SortStableFunc(result.Items, unikornv1.CompareKubernetesCluster) - return c.convertList(result), nil + return convertList(result), nil } // get returns the cluster. @@ -212,17 +212,18 @@ func (c *Client) createIdentity(ctx context.Context, organizationID, projectID, }, } - request := regionapi.PostApiV1OrganizationsOrganizationIDProjectsProjectIDRegionsRegionIDIdentitiesJSONRequestBody{ + request := regionapi.PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesJSONRequestBody{ Metadata: coreapi.ResourceWriteMetadata{ Name: "kubernetes-cluster-" + clusterID, Description: util.ToPointer("Identity for Kubernetes cluster " + clusterID), }, Spec: regionapi.IdentityWriteSpec{ - Tags: &tags, + RegionId: regionID, + Tags: &tags, }, } - resp, err := c.region.PostApiV1OrganizationsOrganizationIDProjectsProjectIDRegionsRegionIDIdentitiesWithResponse(ctx, organizationID, projectID, regionID, request) + resp, err := c.region.PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitiesWithResponse(ctx, organizationID, projectID, request) if err != nil { return nil, errors.OAuth2ServerError("unable to create identity").WithError(err) } @@ -234,8 +235,8 @@ func (c *Client) createIdentity(ctx context.Context, organizationID, projectID, return resp.JSON201, nil } -func (c *Client) getExternalNetworks(ctx context.Context, organizationID, projectID, regionID string) (regionapi.ExternalNetworks, error) { - resp, err := c.region.GetApiV1OrganizationsOrganizationIDProjectsProjectIDRegionsRegionIDExternalnetworksWithResponse(ctx, organizationID, projectID, regionID) +func (c *Client) getExternalNetworks(ctx context.Context, organizationID, regionID string) (regionapi.ExternalNetworks, error) { + resp, err := c.region.GetApiV1OrganizationsOrganizationIDRegionsRegionIDExternalnetworksWithResponse(ctx, organizationID, regionID) if err != nil { return nil, errors.OAuth2ServerError("unable to get external networks").WithError(err) } @@ -247,7 +248,7 @@ func (c *Client) getExternalNetworks(ctx context.Context, organizationID, projec return *resp.JSON200, 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, regionID string, identity *regionapi.IdentityRead, cluster *unikornv1.KubernetesCluster) error { // Save the identity ID for later cleanup. if cluster.Annotations == nil { cluster.Annotations = map[string]string{} @@ -258,7 +259,7 @@ func (c *Client) applyCloudSpecificConfiguration(ctx context.Context, organizati // Setup the provider specific stuff, this should be as minial as possible! switch identity.Spec.Type { case regionapi.Openstack: - externalNetworks, err := c.getExternalNetworks(ctx, organizationID, projectID, regionID) + externalNetworks, err := c.getExternalNetworks(ctx, organizationID, regionID) if err != nil { return err } @@ -267,13 +268,13 @@ func (c *Client) applyCloudSpecificConfiguration(ctx context.Context, organizati return errors.OAuth2ServerError("no external networks present") } - cloudConfig, err := base64.URLEncoding.DecodeString(identity.Spec.Openstack.CloudConfig) + cloudConfig, err := base64.URLEncoding.DecodeString(*identity.Spec.Openstack.CloudConfig) if err != nil { return errors.OAuth2ServerError("failed to decode cloud config").WithError(err) } cluster.Spec.Openstack = &unikornv1.KubernetesClusterOpenstackSpec{ - Cloud: &identity.Spec.Openstack.Cloud, + Cloud: identity.Spec.Openstack.Cloud, CloudConfig: &cloudConfig, ExternalNetworkID: &externalNetworks[0].Id, } @@ -285,46 +286,41 @@ func (c *Client) applyCloudSpecificConfiguration(ctx context.Context, organizati } // Create creates the implicit cluster indentified by the JTW claims. -func (c *Client) Create(ctx context.Context, organizationID, projectID string, request *openapi.KubernetesClusterWrite) error { +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) if err != nil { - return err + return nil, err } // Implicitly create the controller manager. if request.Spec.ClusterManagerId == nil { clusterManager, err := clustermanager.NewClient(c.client).CreateImplicit(ctx, organizationID, projectID) if err != nil { - return err + return nil, err } request.Spec.ClusterManagerId = util.ToPointer(clusterManager.Name) } - cluster, err := c.generate(ctx, namespace, organizationID, projectID, request) + cluster, err := newGenerator(c.client, c.options, c.region, namespace.Name, organizationID, projectID).generate(ctx, request) if err != nil { - return err + return nil, err } identity, err := c.createIdentity(ctx, organizationID, projectID, request.Spec.RegionId, cluster.Name) if err != nil { - return err + return nil, err } - if err := c.applyCloudSpecificConfiguration(ctx, organizationID, projectID, request.Spec.RegionId, identity, cluster); err != nil { - return err + if err := c.applyCloudSpecificConfiguration(ctx, organizationID, request.Spec.RegionId, identity, cluster); err != nil { + return nil, err } if err := c.client.Create(ctx, cluster); err != nil { - // TODO: we can do a cached lookup to save the API traffic. - if kerrors.IsAlreadyExists(err) { - return errors.HTTPConflict() - } - - return errors.OAuth2ServerError("failed to create cluster").WithError(err) + return nil, errors.OAuth2ServerError("failed to create cluster").WithError(err) } - return nil + return convert(cluster), nil } // Delete deletes the implicit cluster indentified by the JTW claims. @@ -372,7 +368,7 @@ func (c *Client) Update(ctx context.Context, organizationID, projectID, clusterI return err } - required, err := c.generate(ctx, namespace, organizationID, projectID, request) + required, err := newGenerator(c.client, c.options, c.region, namespace.Name, organizationID, projectID).generate(ctx, request) if err != nil { return err } diff --git a/pkg/server/handler/cluster/conversion.go b/pkg/server/handler/cluster/conversion.go index 20e070ab..843186dc 100644 --- a/pkg/server/handler/cluster/conversion.go +++ b/pkg/server/handler/cluster/conversion.go @@ -33,14 +33,43 @@ import ( "github.com/unikorn-cloud/kubernetes/pkg/server/handler/applicationbundle" regionapi "github.com/unikorn-cloud/region/pkg/openapi" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + + "sigs.k8s.io/controller-runtime/pkg/client" ) var ( ErrResourceLookup = goerrors.New("could not find the requested resource") ) +// generator wraps up the myriad things we need to pass around as an object +// rather than a whole bunch of arguments. +type generator struct { + // client allows Kubernetes access. + client client.Client + // options allows access to resource defaults. + options *Options + // region is a client to access regions. + region regionapi.ClientWithResponsesInterface + // namespace the resource is provisioned in. + namespace string + // organizationID is the unique organization identifier. + organizationID string + // projectID is the unique project identifier. + projectID string +} + +func newGenerator(client client.Client, options *Options, region regionapi.ClientWithResponsesInterface, namespace, organizationID, projectID string) *generator { + return &generator{ + client: client, + options: options, + region: region, + namespace: namespace, + organizationID: organizationID, + projectID: projectID, + } +} + // convertMachine converts from a custom resource into the API definition. func convertMachine(in *unikornv1.MachineGeneric) *openapi.MachinePool { machine := &openapi.MachinePool{ @@ -89,7 +118,7 @@ func convertWorkloadPools(in *unikornv1.KubernetesCluster) []openapi.KubernetesC } // convert converts from a custom resource into the API definition. -func (c *Client) convert(in *unikornv1.KubernetesCluster) *openapi.KubernetesClusterRead { +func convert(in *unikornv1.KubernetesCluster) *openapi.KubernetesClusterRead { provisioningStatus := coreopenapi.ResourceProvisioningStatusUnknown if condition, err := in.StatusConditionRead(unikornv1core.ConditionAvailable); err == nil { @@ -110,19 +139,19 @@ func (c *Client) convert(in *unikornv1.KubernetesCluster) *openapi.KubernetesClu } // uconvertList converts from a custom resource list into the API definition. -func (c *Client) convertList(in *unikornv1.KubernetesClusterList) openapi.KubernetesClusters { +func convertList(in *unikornv1.KubernetesClusterList) openapi.KubernetesClusters { out := make(openapi.KubernetesClusters, len(in.Items)) for i := range in.Items { - out[i] = *c.convert(&in.Items[i]) + out[i] = *convert(&in.Items[i]) } return out } // defaultApplicationBundle returns a default application bundle. -func (c *Client) defaultApplicationBundle(ctx context.Context) (*unikornv1.KubernetesClusterApplicationBundle, error) { - applicationBundles, err := applicationbundle.NewClient(c.client).ListCluster(ctx) +func (g *generator) defaultApplicationBundle(ctx context.Context) (*unikornv1.KubernetesClusterApplicationBundle, error) { + applicationBundles, err := applicationbundle.NewClient(g.client).ListCluster(ctx) if err != nil { return nil, err } @@ -150,8 +179,8 @@ func (c *Client) defaultApplicationBundle(ctx context.Context) (*unikornv1.Kuber // one that doesxn't have any GPUs. The provider ensures the "nost cost-effective" // comes first. // TODO: we should allow this to be configured per region. -func (c *Client) defaultControlPlaneFlavor(ctx context.Context, organizationID, projectID, regionID string) (*regionapi.Flavor, error) { - resp, err := c.region.GetApiV1OrganizationsOrganizationIDProjectsProjectIDRegionsRegionIDFlavorsWithResponse(ctx, organizationID, projectID, regionID) +func (g *generator) defaultControlPlaneFlavor(ctx context.Context, request *openapi.KubernetesClusterWrite) (*regionapi.Flavor, error) { + resp, err := g.region.GetApiV1OrganizationsOrganizationIDRegionsRegionIDFlavorsWithResponse(ctx, g.organizationID, request.Spec.RegionId) if err != nil { return nil, err } @@ -169,8 +198,8 @@ func (c *Client) defaultControlPlaneFlavor(ctx context.Context, organizationID, // defaultImage returns a default image for either control planes or workload pools // based on the specified Kubernetes version. -func (c *Client) defaultImage(ctx context.Context, organizationID, projectID, regionID, version string) (*regionapi.Image, error) { - resp, err := c.region.GetApiV1OrganizationsOrganizationIDProjectsProjectIDRegionsRegionIDImagesWithResponse(ctx, organizationID, projectID, regionID) +func (g *generator) defaultImage(ctx context.Context, request *openapi.KubernetesClusterWrite, version string) (*regionapi.Image, error) { + resp, err := g.region.GetApiV1OrganizationsOrganizationIDRegionsRegionIDImagesWithResponse(ctx, g.organizationID, request.Spec.RegionId) if err != nil { return nil, err } @@ -189,13 +218,13 @@ func (c *Client) defaultImage(ctx context.Context, organizationID, projectID, re } // generateNetwork generates the network part of a cluster. -func (c *Client) generateNetwork() *unikornv1.KubernetesClusterNetworkSpec { +func (g *generator) generateNetwork() *unikornv1.KubernetesClusterNetworkSpec { // Grab some defaults (as these are in the right format already) // the override with anything coming in from the API, if set. - nodeNetwork := c.options.NodeNetwork - serviceNetwork := c.options.ServiceNetwork - podNetwork := c.options.PodNetwork - dnsNameservers := c.options.DNSNameservers + nodeNetwork := g.options.NodeNetwork + serviceNetwork := g.options.ServiceNetwork + podNetwork := g.options.PodNetwork + dnsNameservers := g.options.DNSNameservers network := &unikornv1.KubernetesClusterNetworkSpec{ NodeNetwork: &unikornv1.IPv4Prefix{IPNet: nodeNetwork}, @@ -208,12 +237,12 @@ func (c *Client) generateNetwork() *unikornv1.KubernetesClusterNetworkSpec { } // generateMachineGeneric generates a generic machine part of the cluster. -func (c *Client) generateMachineGeneric(ctx context.Context, organizationID, projectID string, options *openapi.KubernetesClusterSpec, m *openapi.MachinePool, flavor *regionapi.Flavor) (*unikornv1.MachineGeneric, error) { +func (g *generator) generateMachineGeneric(ctx context.Context, request *openapi.KubernetesClusterWrite, m *openapi.MachinePool, flavor *regionapi.Flavor) (*unikornv1.MachineGeneric, error) { if m.Replicas == nil { m.Replicas = util.ToPointer(3) } - image, err := c.defaultImage(ctx, organizationID, projectID, options.RegionId, options.Version) + image, err := g.defaultImage(ctx, request, request.Spec.Version) if err != nil { return nil, err } @@ -239,9 +268,9 @@ func (c *Client) generateMachineGeneric(ctx context.Context, organizationID, pro } // generateControlPlane generates the control plane part of a cluster. -func (c *Client) generateControlPlane(ctx context.Context, organizationID, projectID string, options *openapi.KubernetesClusterSpec) (*unikornv1.KubernetesClusterControlPlaneSpec, error) { +func (g *generator) generateControlPlane(ctx context.Context, request *openapi.KubernetesClusterWrite) (*unikornv1.KubernetesClusterControlPlaneSpec, error) { // Add in any missing defaults. - resource, err := c.defaultControlPlaneFlavor(ctx, organizationID, projectID, options.RegionId) + resource, err := g.defaultControlPlaneFlavor(ctx, request) if err != nil { return nil, err } @@ -250,7 +279,7 @@ func (c *Client) generateControlPlane(ctx context.Context, organizationID, proje FlavorId: &resource.Metadata.Id, } - machine, err := c.generateMachineGeneric(ctx, organizationID, projectID, options, machineOptions, resource) + machine, err := g.generateMachineGeneric(ctx, request, machineOptions, resource) if err != nil { return nil, err } @@ -263,18 +292,18 @@ func (c *Client) generateControlPlane(ctx context.Context, organizationID, proje } // generateWorkloadPools generates the workload pools part of a cluster. -func (c *Client) generateWorkloadPools(ctx context.Context, organizationID, projectID string, options *openapi.KubernetesClusterSpec) (*unikornv1.KubernetesClusterWorkloadPoolsSpec, error) { +func (g *generator) generateWorkloadPools(ctx context.Context, request *openapi.KubernetesClusterWrite) (*unikornv1.KubernetesClusterWorkloadPoolsSpec, error) { workloadPools := &unikornv1.KubernetesClusterWorkloadPoolsSpec{} - for i := range options.WorkloadPools { - pool := &options.WorkloadPools[i] + for i := range request.Spec.WorkloadPools { + pool := &request.Spec.WorkloadPools[i] - flavor, err := c.lookupFlavor(ctx, organizationID, projectID, options.RegionId, *pool.Machine.FlavorId) + flavor, err := g.lookupFlavor(ctx, request, *pool.Machine.FlavorId) if err != nil { return nil, err } - machine, err := c.generateMachineGeneric(ctx, organizationID, projectID, options, &pool.Machine, flavor) + machine, err := g.generateMachineGeneric(ctx, request, &pool.Machine, flavor) if err != nil { return nil, err } @@ -328,8 +357,8 @@ func (c *Client) generateWorkloadPools(ctx context.Context, organizationID, proj // lookupFlavor resolves the flavor from its name. // NOTE: It looks like garbage performance, but the provider should be memoized... -func (c *Client) lookupFlavor(ctx context.Context, organizationID, projectID, regionID, id string) (*regionapi.Flavor, error) { - resp, err := c.region.GetApiV1OrganizationsOrganizationIDProjectsProjectIDRegionsRegionIDFlavorsWithResponse(ctx, organizationID, projectID, regionID) +func (g *generator) lookupFlavor(ctx context.Context, request *openapi.KubernetesClusterWrite, id string) (*regionapi.Flavor, error) { + resp, err := g.region.GetApiV1OrganizationsOrganizationIDRegionsRegionIDFlavorsWithResponse(ctx, g.organizationID, request.Spec.RegionId) if err != nil { return nil, err } @@ -349,9 +378,9 @@ func (c *Client) lookupFlavor(ctx context.Context, organizationID, projectID, re // installNvidiaOperator installs the nvidia operator if any workload pool flavor // has a GPU in it. -func (c *Client) installNvidiaOperator(ctx context.Context, organizationID, projectID string, request *openapi.KubernetesClusterWrite, cluster *unikornv1.KubernetesCluster) error { +func (g *generator) installNvidiaOperator(ctx context.Context, request *openapi.KubernetesClusterWrite, cluster *unikornv1.KubernetesCluster) error { for _, pool := range request.Spec.WorkloadPools { - flavor, err := c.lookupFlavor(ctx, organizationID, projectID, request.Spec.RegionId, *pool.Machine.FlavorId) + flavor, err := g.lookupFlavor(ctx, request, *pool.Machine.FlavorId) if err != nil { return err } @@ -382,31 +411,31 @@ func installClusterAutoscaler(cluster *unikornv1.KubernetesCluster) { // generate generates the full cluster custom resource. // TODO: there are a lot of parameters being passed about, we should make this // a struct and pass them as a single blob. -func (c *Client) generate(ctx context.Context, namespace *corev1.Namespace, organizationID, projectID string, request *openapi.KubernetesClusterWrite) (*unikornv1.KubernetesCluster, error) { - kubernetesControlPlane, err := c.generateControlPlane(ctx, organizationID, projectID, &request.Spec) +func (g *generator) generate(ctx context.Context, request *openapi.KubernetesClusterWrite) (*unikornv1.KubernetesCluster, error) { + kubernetesControlPlane, err := g.generateControlPlane(ctx, request) if err != nil { return nil, err } - kubernetesWorkloadPools, err := c.generateWorkloadPools(ctx, organizationID, projectID, &request.Spec) + kubernetesWorkloadPools, err := g.generateWorkloadPools(ctx, request) if err != nil { return nil, err } - applicationBundle, err := c.defaultApplicationBundle(ctx) + applicationBundle, err := g.defaultApplicationBundle(ctx) if err != nil { return nil, err } cluster := &unikornv1.KubernetesCluster{ - ObjectMeta: conversion.NewObjectMetadata(&request.Metadata, namespace.Name).WithOrganization(organizationID).WithProject(projectID).Get(ctx), + ObjectMeta: conversion.NewObjectMetadata(&request.Metadata, g.namespace).WithOrganization(g.organizationID).WithProject(g.projectID).Get(ctx), Spec: unikornv1.KubernetesClusterSpec{ RegionID: request.Spec.RegionId, ClusterManagerID: *request.Spec.ClusterManagerId, Version: util.ToPointer(unikornv1.SemanticVersion(request.Spec.Version)), ApplicationBundle: &applicationBundle.Name, ApplicationBundleAutoUpgrade: &unikornv1.ApplicationBundleAutoUpgradeSpec{}, - Network: c.generateNetwork(), + Network: g.generateNetwork(), ControlPlane: kubernetesControlPlane, WorkloadPools: kubernetesWorkloadPools, Features: &unikornv1.KubernetesClusterFeaturesSpec{}, @@ -415,7 +444,7 @@ func (c *Client) generate(ctx context.Context, namespace *corev1.Namespace, orga installClusterAutoscaler(cluster) - if err := c.installNvidiaOperator(ctx, organizationID, projectID, request, cluster); err != nil { + if err := g.installNvidiaOperator(ctx, request, cluster); err != nil { return nil, err } diff --git a/pkg/server/handler/clustermanager/client.go b/pkg/server/handler/clustermanager/client.go index eee8a1b3..690bc5ad 100644 --- a/pkg/server/handler/clustermanager/client.go +++ b/pkg/server/handler/clustermanager/client.go @@ -88,7 +88,7 @@ func (c *Client) CreateImplicit(ctx context.Context, organizationID, projectID s }, } - resource, err := c.Create(ctx, organizationID, projectID, request) + resource, err := c.create(ctx, organizationID, projectID, request) if err != nil { return nil, err } @@ -207,7 +207,7 @@ func (c *Client) generate(ctx context.Context, namespace *corev1.Namespace, orga } // Create creates a control plane. -func (c *Client) Create(ctx context.Context, organizationID, projectID string, request *openapi.ClusterManagerWrite) (*unikornv1.ClusterManager, error) { +func (c *Client) create(ctx context.Context, organizationID, projectID string, request *openapi.ClusterManagerWrite) (*unikornv1.ClusterManager, error) { namespace, err := common.New(c.client).ProjectNamespace(ctx, organizationID, projectID) if err != nil { return nil, err @@ -234,6 +234,15 @@ func (c *Client) Create(ctx context.Context, organizationID, projectID string, r return resource, nil } +func (c *Client) Create(ctx context.Context, organizationID, projectID string, request *openapi.ClusterManagerWrite) (*openapi.ClusterManagerRead, error) { + result, err := c.create(ctx, organizationID, projectID, request) + if err != nil { + return nil, err + } + + return c.convert(result), nil +} + // Delete deletes the control plane. func (c *Client) Delete(ctx context.Context, organizationID, projectID, clusterManagerID string) error { namespace, err := common.New(c.client).ProjectNamespace(ctx, organizationID, projectID) diff --git a/pkg/server/handler/handler.go b/pkg/server/handler/handler.go index 85ab0221..7ecc7682 100644 --- a/pkg/server/handler/handler.go +++ b/pkg/server/handler/handler.go @@ -104,13 +104,14 @@ func (h *Handler) PostApiV1OrganizationsOrganizationIDProjectsProjectIDClusterma return } - if _, err := clustermanager.NewClient(h.client).Create(r.Context(), organizationID, projectID, request); err != nil { + result, err := clustermanager.NewClient(h.client).Create(r.Context(), organizationID, projectID, request) + if err != nil { errors.HandleError(w, r, err) return } h.setUncacheable(w) - w.WriteHeader(http.StatusAccepted) + util.WriteJSONResponse(w, r, http.StatusAccepted, result) } func (h *Handler) DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDClustermanagersClusterManagerID(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, projectID openapi.ProjectIDParameter, clusterManagerID openapi.ClusterManagerIDParameter) { @@ -190,13 +191,14 @@ func (h *Handler) PostApiV1OrganizationsOrganizationIDProjectsProjectIDClusters( return } - if err := cluster.NewClient(h.client, h.namespace, &h.options.Cluster, region).Create(r.Context(), organizationID, projectID, request); err != nil { + result, err := cluster.NewClient(h.client, h.namespace, &h.options.Cluster, region).Create(r.Context(), organizationID, projectID, request) + if err != nil { errors.HandleError(w, r, err) return } h.setUncacheable(w) - w.WriteHeader(http.StatusAccepted) + util.WriteJSONResponse(w, r, http.StatusAccepted, result) } func (h *Handler) DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDClustersClusterID(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, projectID openapi.ProjectIDParameter, clusterID openapi.ClusterIDParameter) {