Skip to content

Commit

Permalink
Move Identity Provisioning into Controller
Browse files Browse the repository at this point in the history
This makes identity provisioning asynchronous and idempotent, which is
far more fault tolerant and can hide long provisioning times better at
the front end - the cost just disappears into cluster provisioning.  Two
very important things to note, first the openstack configuration should
live in a separate CR, second we've now got a problem in that the
kubernetes service needs to poll for provisioning, which cannot be done
without a token...
  • Loading branch information
spjmurray committed Aug 9, 2024
1 parent cf8049e commit e4430fa
Show file tree
Hide file tree
Showing 15 changed files with 406 additions and 247 deletions.
18 changes: 10 additions & 8 deletions charts/region/crds/region.unikorn-cloud.org_identities.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ spec:
description: OpenStack is populated when the provider type is set
to "openstack".
properties:
applicationCredentialID:
description: ApplicationCredentialID is the ID of the user's application
credential.
type: string
applicationCredentialSecret:
description: ApplicationCredentialSecret is the one-time secret
for the application credential.
type: string
cloud:
description: Cloud is the cloud name in the cloud config to use.
type: string
Expand All @@ -82,12 +90,6 @@ spec:
userID:
description: UserID is the ID of the user created for the identity.
type: string
required:
- cloud
- cloudConfig
- password
- projectID
- userID
type: object
pause:
description: Pause, if true, will inhibit reconciliation.
Expand Down Expand Up @@ -169,8 +171,8 @@ spec:
type: object
required:
- spec
- status
type: object
served: true
storage: true
subresources: {}
subresources:
status: {}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ spec:
type: object
required:
- spec
- status
type: object
served: true
storage: true
Expand Down
8 changes: 8 additions & 0 deletions charts/region/templates/identity-controller/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,11 @@ rules:
verbs:
- list
- watch
- apiGroups:
- region.unikorn-cloud.org
resources:
- regions
verbs:
- list
- watch

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/oapi-codegen/runtime v1.1.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
github.com/unikorn-cloud/core v0.1.63
github.com/unikorn-cloud/core v0.1.64
github.com/unikorn-cloud/identity v0.2.29
go.opentelemetry.io/otel v1.28.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65E
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/unikorn-cloud/core v0.1.63 h1:Jl/xuoGRKESMXhS1+apcaS/1I776agTyT75BGz9AKBA=
github.com/unikorn-cloud/core v0.1.63/go.mod h1:JcUIQW3+oiZPUQmOlENw3OCi35IBxPKa+J4MbP3TO7k=
github.com/unikorn-cloud/core v0.1.64 h1:4GqACg1YOmUrDo5euXz0yi753Mzx1TBg7fioRKysb1g=
github.com/unikorn-cloud/core v0.1.64/go.mod h1:JcUIQW3+oiZPUQmOlENw3OCi35IBxPKa+J4MbP3TO7k=
github.com/unikorn-cloud/identity v0.2.29 h1:kKEJmh6tjjdvZWYdZhyRewG3aHf9wmWwG5C/kb+Rm9A=
github.com/unikorn-cloud/identity v0.2.29/go.mod h1:ujrL+6kRUrPIk4Z0Yc12A+FDy6L4b2Hgzz6oGZlKfGI=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down
19 changes: 12 additions & 7 deletions pkg/apis/unikorn/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,14 +279,15 @@ type IdentityList struct {
// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:resource:scope=Namespaced,categories=unikorn
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="provider",type="string",JSONPath=".spec.provider"
// +kubebuilder:printcolumn:name="status",type="string",JSONPath=".status.conditions[?(@.type==\"Available\")].reason"
// +kubebuilder:printcolumn:name="age",type="date",JSONPath=".metadata.creationTimestamp"
type Identity struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec IdentitySpec `json:"spec"`
Status IdentityStatus `json:"status"`
Status IdentityStatus `json:"status,omitempty"`
}

// IdentitySpec stores any state necessary to manage identity.
Expand All @@ -304,15 +305,19 @@ type IdentitySpec struct {

type IdentitySpecOpenStack struct {
// CloudConfig is a client compatible cloud configuration.
CloudConfig []byte `json:"cloudConfig"`
CloudConfig []byte `json:"cloudConfig,omitempty"`
// Cloud is the cloud name in the cloud config to use.
Cloud string `json:"cloud"`
Cloud *string `json:"cloud,omitempty"`
// UserID is the ID of the user created for the identity.
UserID string `json:"userID"`
UserID *string `json:"userID,omitempty"`
// Password is the login for the user.
Password string `json:"password"`
Password *string `json:"password,omitempty"`
// ProjectID is the ID of the project created for the identity.
ProjectID string `json:"projectID"`
ProjectID *string `json:"projectID,omitempty"`
// ApplicationCredentialID is the ID of the user's application credential.
ApplicationCredentialID *string `json:"applicationCredentialID,omitempty"`
// ApplicationCredentialSecret is the one-time secret for the application credential.
ApplicationCredentialSecret *string `json:"applicationCredentialSecret,omitempty"`
// ServerGroupID is the ID of the server group created for the identity.
ServerGroupID *string `json:"serverGroupID,omitempty"`
}
Expand Down Expand Up @@ -340,7 +345,7 @@ type PhysicalNetwork struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec PhysicalNetworkSpec `json:"spec"`
Status PhysicalNetworkStatus `json:"status"`
Status PhysicalNetworkStatus `json:"status,omitempty"`
}

type PhysicalNetworkSpec struct {
Expand Down
30 changes: 30 additions & 0 deletions pkg/apis/unikorn/v1alpha1/zz_generated.deepcopy.go

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

81 changes: 60 additions & 21 deletions pkg/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,17 +313,21 @@ func convertIdentity(in *unikornv1.Identity) *openapi.IdentityRead {
case unikornv1.ProviderOpenstack:
out.Spec.Type = openapi.Openstack

cloudConfig := base64.URLEncoding.EncodeToString(in.Spec.OpenStack.CloudConfig)

out.Spec.Openstack = &openapi.IdentitySpecOpenStack{
CloudConfig: cloudConfig,
Cloud: in.Spec.OpenStack.Cloud,
UserId: in.Spec.OpenStack.UserID,
ProjectId: in.Spec.OpenStack.ProjectID,
}

if in.Spec.OpenStack.ServerGroupID != nil {
out.Spec.Openstack.ServerGroupId = in.Spec.OpenStack.ServerGroupID
if in.Spec.OpenStack != nil {
out.Spec.Openstack = &openapi.IdentitySpecOpenStack{
Cloud: in.Spec.OpenStack.Cloud,
UserId: in.Spec.OpenStack.UserID,
ProjectId: in.Spec.OpenStack.ProjectID,
}

if in.Spec.OpenStack.CloudConfig != nil {
cloudConfig := base64.URLEncoding.EncodeToString(in.Spec.OpenStack.CloudConfig)
out.Spec.Openstack.CloudConfig = &cloudConfig
}

if in.Spec.OpenStack.ServerGroupID != nil {
out.Spec.Openstack.ServerGroupId = in.Spec.OpenStack.ServerGroupID
}
}
}

Expand Down Expand Up @@ -366,6 +370,29 @@ func (h *Handler) GetApiV1OrganizationsOrganizationIDIdentities(w http.ResponseW
util.WriteJSONResponse(w, r, http.StatusOK, convertIdentityList(result))
}

func generateTag(in openapi.Tag) unikornv1.Tag {
out := unikornv1.Tag{
Name: in.Name,
Value: in.Value,
}

return out
}

func generateTagList(in *openapi.TagList) unikornv1.TagList {
if in == nil {
return nil
}

out := make(unikornv1.TagList, len(*in))

for i := range *in {
out[i] = generateTag((*in)[i])
}

return out
}

func (h *Handler) PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentities(w http.ResponseWriter, r *http.Request, organizationID openapi.OrganizationIDParameter, projectID openapi.ProjectIDParameter) {
if err := rbac.AllowProjectScope(r.Context(), "identities", identityapi.Create, organizationID, projectID); err != nil {
errors.HandleError(w, r, err)
Expand All @@ -381,13 +408,26 @@ func (h *Handler) PostApiV1OrganizationsOrganizationIDProjectsProjectIDIdentitie

provider, err := region.NewClient(h.client, h.namespace).Provider(r.Context(), request.Spec.RegionId)
if err != nil {
errors.HandleError(w, r, err)
errors.HandleError(w, r, errors.OAuth2ServerError("unable to get region provider").WithError(err))
return
}

identity, err := provider.CreateIdentity(r.Context(), organizationID, projectID, request)
region, err := provider.Region(r.Context())
if err != nil {
errors.HandleError(w, r, err)
errors.HandleError(w, r, errors.OAuth2ServerError("unable to get region").WithError(err))
return
}

identity := &unikornv1.Identity{
ObjectMeta: conversion.NewObjectMetadata(&request.Metadata, h.namespace).WithOrganization(organizationID).WithProject(projectID).WithLabel(constants.RegionLabel, request.Spec.RegionId).Get(r.Context()),
Spec: unikornv1.IdentitySpec{
Tags: generateTagList(request.Spec.Tags),
Provider: region.Spec.Provider,
},
}

if err := h.client.Create(r.Context(), identity); err != nil {
errors.HandleError(w, r, errors.OAuth2ServerError("unable to create identity").WithError(err))
return
}

Expand All @@ -407,14 +447,13 @@ func (h *Handler) DeleteApiV1OrganizationsOrganizationIDProjectsProjectIDIdentit
return
}

provider, err := region.NewClient(h.client, h.namespace).Provider(r.Context(), identity.Labels[constants.RegionLabel])
if err != nil {
errors.HandleError(w, r, err)
return
}
if err := h.client.Delete(r.Context(), identity); err != nil {
if kerrors.IsNotFound(err) {
errors.HandleError(w, r, errors.HTTPNotFound().WithError(err))
return
}

if err := provider.DeleteIdentity(r.Context(), identity); err != nil {
errors.HandleError(w, r, errors.OAuth2ServerError("failed to delete identity").WithError(err))
errors.HandleError(w, r, errors.OAuth2ServerError("unable to delete identity").WithError(err))
return
}

Expand Down
Loading

0 comments on commit e4430fa

Please sign in to comment.