Skip to content

Commit

Permalink
Wire Up Identity Provisioner (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
spjmurray authored Jun 10, 2024
1 parent 84b4fa7 commit 242270d
Show file tree
Hide file tree
Showing 13 changed files with 375 additions and 236 deletions.
4 changes: 2 additions & 2 deletions charts/region/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: A Helm chart for deploying Unikorn's Region Controller

type: application

version: v0.1.1
appVersion: v0.1.1
version: v0.1.2
appVersion: v0.1.2

icon: https://raw.githubusercontent.com/unikorn-cloud/unikorn/main/icons/default.png
91 changes: 91 additions & 0 deletions charts/region/crds/region.unikorn-cloud.org_identities.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.14.0
name: identities.region.unikorn-cloud.org
spec:
group: region.unikorn-cloud.org
names:
categories:
- unikorn
kind: Identity
listKind: IdentityList
plural: identities
singular: identity
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.provider
name: provider
type: string
- jsonPath: .status.conditions[?(@.type=="Available")].reason
name: status
type: string
- jsonPath: .metadata.creationTimestamp
name: age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: |-
Identity defines an on-demand cloud identity. The region controller must
create any resources necessary to provide dynamic provisioning of clusters
e.g. compute, storage and networking. This resource is used for persistence
of information by the controller and not for manual lifecycle management.
Any credentials should not be stored unless absolutely necessary, and should
be passed to a client on initial identity creation only.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: IdentitySpec stores any state necessary to manage identity.
properties:
openstack:
description: OpenStack is populated when the provider type is set
to "openstack".
properties:
projectID:
description: ProjectIS is the ID of the project created for the
identity.
type: string
userID:
description: UserID is the ID of the user created for the identity.
type: string
required:
- projectID
- userID
type: object
provider:
description: Provider defines the provider type.
enum:
- openstack
type: string
required:
- provider
type: object
status:
type: object
required:
- spec
- status
type: object
served: true
storage: true
subresources: {}
9 changes: 9 additions & 0 deletions charts/region/templates/region-controller/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ rules:
verbs:
- list
- watch
- apiGroups:
- region.unikorn-cloud.org
resources:
- identities
verbs:
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
Expand Down
4 changes: 2 additions & 2 deletions pkg/apis/unikorn/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ type IdentitySpec struct {

type IdentitySpecOpenStack struct {
// UserID is the ID of the user created for the identity.
UserID string
UserID string `json:"userID"`
// ProjectIS is the ID of the project created for the identity.
ProjectID string
ProjectID string `json:"projectID"`
}

type IdentityStatus struct {
Expand Down
65 changes: 60 additions & 5 deletions pkg/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,19 @@ limitations under the License.
package handler

import (
"cmp"
"encoding/base64"
"fmt"
"net/http"
"sort"
"slices"
"time"

coreopenapi "github.com/unikorn-cloud/core/pkg/openapi"
"github.com/unikorn-cloud/core/pkg/server/conversion"
"github.com/unikorn-cloud/core/pkg/server/errors"
"github.com/unikorn-cloud/core/pkg/server/middleware/openapi/oidc"
coreutil "github.com/unikorn-cloud/core/pkg/util"
unikornv1 "github.com/unikorn-cloud/region/pkg/apis/unikorn/v1alpha1"
"github.com/unikorn-cloud/region/pkg/handler/region"
"github.com/unikorn-cloud/region/pkg/openapi"
"github.com/unikorn-cloud/region/pkg/providers"
Expand Down Expand Up @@ -128,8 +132,15 @@ func (h *Handler) GetApiV1RegionsRegionIDFlavors(w http.ResponseWriter, r *http.
return
}

// Apply ordering guarantees.
sort.Stable(result)
// Apply ordering guarantees, ascending order with GPUs taking precedence over
// CPUs.
slices.SortFunc(result, func(a, b providers.Flavor) int {
if v := cmp.Compare(a.GPUs, b.GPUs); v != 0 {
return v
}

return cmp.Compare(a.CPUs, b.CPUs)
})

out := make(openapi.Flavors, len(result))

Expand Down Expand Up @@ -173,8 +184,10 @@ func (h *Handler) GetApiV1RegionsRegionIDImages(w http.ResponseWriter, r *http.R
return
}

// Apply ordering guarantees.
sort.Stable(result)
// Apply ordering guarantees, ordered by name.
slices.SortFunc(result, func(a, b providers.Image) int {
return cmp.Compare(a.Name, b.Name)
})

out := make(openapi.Images, len(result))

Expand All @@ -186,7 +199,49 @@ func (h *Handler) GetApiV1RegionsRegionIDImages(w http.ResponseWriter, r *http.R
util.WriteJSONResponse(w, r, http.StatusOK, out)
}

func convertCloudConfig(identity *unikornv1.Identity, in *providers.CloudConfig) *openapi.IdentityRead {
out := &openapi.IdentityRead{
Metadata: conversion.ProjectScopedResourceReadMetadata(identity, coreopenapi.ResourceProvisioningStatusProvisioned),
}

switch in.Type {
case providers.ProviderTypeOpenStack:
out.Spec = openapi.IdentitySpec{
Type: openapi.Openstack,
Openstack: &openapi.IdentitySpecOpenStack{
Cloud: in.OpenStack.Credentials.Cloud,
CloudConfig: base64.URLEncoding.EncodeToString(in.OpenStack.Credentials.CloudConfig),
UserId: in.OpenStack.State.UserID,
ProjectId: in.OpenStack.State.ProjectID,
},
}
}

return out
}

func (h *Handler) PostApiV1RegionsRegionIDIdentities(w http.ResponseWriter, r *http.Request, regionID openapi.RegionIDParameter) {
request := &openapi.IdentityWrite{}

if err := util.ReadJSONBody(r, request); err != nil {
errors.HandleError(w, r, err)
return
}

provider, err := region.NewClient(h.client, h.namespace).Provider(r.Context(), regionID)
if err != nil {
errors.HandleError(w, r, err)
return
}

identity, cloudconfig, err := provider.CreateIdentity(r.Context(), &providers.ClusterInfo{})
if err != nil {
errors.HandleError(w, r, err)
return
}

h.setCacheable(w)
util.WriteJSONResponse(w, r, http.StatusCreated, convertCloudConfig(identity, cloudconfig))
}

func (h *Handler) DeleteApiV1RegionsRegionIDIdentitiesIdentityID(w http.ResponseWriter, r *http.Request, regionID openapi.RegionIDParameter, identityID openapi.IdentityIDParameter) {
Expand Down
130 changes: 66 additions & 64 deletions pkg/openapi/schema.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 242270d

Please sign in to comment.