Skip to content

Commit

Permalink
Tweak OpenAPI Definitions
Browse files Browse the repository at this point in the history
You can externally reference other schemas in OpenAPI, so we can share
things across different microservices (yay), to do this you need to
import the generated types and schema, so this puts that in place.
Secondly we need to tweak the read metadata to add additional scope
where it's required for various views.  Third, we can share some
metadata conversion functions.
  • Loading branch information
spjmurray committed Jun 3, 2024
1 parent 82faf60 commit 5f463af
Show file tree
Hide file tree
Showing 7 changed files with 218 additions and 24 deletions.
10 changes: 6 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ CONTROLLER_TOOLS_VERSION=v0.14.0
# This should be kept in sync with the Kubenetes library versions defined in go.mod.
CODEGEN_VERSION=v0.27.3

OPENAPI_CODEGEN_VERSION=v1.12.4
OPENAPI_CODEGEN_VERSION=v1.16.2

OPENAPI_FILES = pkg/openapi/types.go pkg/openapi/schema.go

# Defined the mock generator version.
MOCKGEN_VERSION=v0.3.0
Expand All @@ -53,7 +55,7 @@ GENCLIENTS = $(MODULE)/$(GENDIR)/clientset

# Main target, builds all binaries.
.PHONY: all
all: $(GENDIR) $(CRDDIR) openapi/types.go
all: $(GENDIR) $(CRDDIR) $(OPENAPI_FILES)

# TODO: we may wamt to consider porting the rest of the CRD and client generation
# stuff over... that said, we don't need the clients really do we, controller-runtime
Expand All @@ -68,11 +70,11 @@ test-unit:
go test -coverpkg ./... -coverprofile cover.out ./...
go tool cover -html cover.out -o cover.html

openapi/types.go: openapi/common.spec.yaml
pkg/openapi/types.go: pkg/openapi/common.spec.yaml
@go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@$(OPENAPI_CODEGEN_VERSION)
oapi-codegen -generate types,skip-prune -package openapi -o $@ $<

openapi/schema.go: openapi/common.spec.yaml
pkg/openapi/schema.go: pkg/openapi/common.spec.yaml
@go install github.com/deepmap/oapi-codegen/cmd/oapi-codegen@$(OPENAPI_CODEGEN_VERSION)
oapi-codegen -generate spec,skip-prune -package openapi -o $@ $<

Expand Down
4 changes: 2 additions & 2 deletions charts/core/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 Core

type: application

version: v0.1.32
appVersion: v0.1.32
version: v0.1.33
appVersion: v0.1.33

icon: https://assets.unikorn-cloud.org/images/logos/dark-on-light/icon.svg
9 changes: 9 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ const (
// This is the default version in the Makefile.
DeveloperVersion = "0.0.0"

// NameLabel is attached to every resource to give it a mutable display
// name. While the character set is limited to [0-9A-Za-z_-.] it is at least
// indexed in etcd which gives us another string to our bow.
NameLabel = "unikorn-cloud.org/name"

// DescriptionAnnotation is optionally attached to a resource to allow
// an unconstriained and verbose description about the resource.
DescriptionAnnotation = "unikorn-cloud.org/description"

// VersionLabel is a label applied to resources so we know the application
// version that was used to create them (and thus what metadata is valid
// for them). Metadata may be upgraded to a later version for any resource.
Expand Down
20 changes: 20 additions & 0 deletions openapi/common.spec.yaml → pkg/openapi/common.spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,23 @@ components:
$ref: '#/components/schemas/resourceProvisioningStatus'
resourceWriteMetadata:
$ref: '#/components/schemas/resourceMetadata'
organizationScopedResourceReadMetadata:
allOf:
- $ref: '#/components/schemas/resourceReadMetadata'
- type: object
required:
- organizationId
properties:
organizationId:
description: The organization identifier the resource belongs to.
type: string
projectScopedResourceReadMetadata:
allOf:
- $ref: '#/components/schemas/organizationScopedResourceReadMetadata'
- type: object
required:
- projectId
properties:
projectId:
description: The project identifier the resource belongs to.
type: string
35 changes: 18 additions & 17 deletions openapi/schema.go → pkg/openapi/schema.go

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

55 changes: 54 additions & 1 deletion openapi/types.go → pkg/openapi/types.go

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

109 changes: 109 additions & 0 deletions pkg/server/conversion/conversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
Copyright 2024 the Unikorn Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package conversion

import (
"unicode"

unikornv1 "github.com/unikorn-cloud/core/pkg/apis/unikorn/v1alpha1"
"github.com/unikorn-cloud/core/pkg/constants"
"github.com/unikorn-cloud/core/pkg/openapi"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/uuid"
)

// ConvertStatusCondition translates from Kubernetes status conditions to API ones.
func ConvertStatusCondition(in *unikornv1.Condition) openapi.ResourceProvisioningStatus {
//nolint:exhaustive
switch in.Reason {
case unikornv1.ConditionReasonProvisioning:
return openapi.Provisioning
case unikornv1.ConditionReasonProvisioned:
return openapi.Provisioned
case unikornv1.ConditionReasonErrored:
return openapi.Error
case unikornv1.ConditionReasonDeprovisioning:
return openapi.Deprovisioning
default:
return openapi.Unknown
}
}

// ResourceReadMetadata extracts generic metadata from a resource for GET APIs.
func ResourceReadMetadata(in metav1.Object, status openapi.ResourceProvisioningStatus) openapi.ResourceReadMetadata {
labels := in.GetLabels()
annotations := in.GetAnnotations()

out := openapi.ResourceReadMetadata{
Id: in.GetName(),
Name: labels[constants.NameLabel],
CreationTime: in.GetCreationTimestamp().Time,
ProvisioningStatus: status,
}

if v, ok := annotations[constants.DescriptionAnnotation]; ok {
out.Description = &v
}

if v := in.GetDeletionTimestamp(); v != nil {
out.DeletionTime = &v.Time
}

return out
}

// generateResourceID creates a valid Kubernetes name from a UUID.
func generateResourceID() string {
for {
// NOTE: Kubernetes UUIDs are based on version 4, aka random,
// so the first character will be a letter eventually, like
// a 6/16 chance: tl;dr infinite loops are... improbable.
if id := uuid.NewUUID(); unicode.IsLetter(rune(id[0])) {
return string(id)
}
}
}

// ObjectMetadata creates Kubernetes object metadata from generic request metadata.
func ObjectMetadata(in *openapi.ResourceWriteMetadata, namespace string, labels map[string]string) metav1.ObjectMeta {
out := metav1.ObjectMeta{
Namespace: namespace,
Name: generateResourceID(),
Labels: map[string]string{
constants.NameLabel: in.Name,
},
}

for k, v := range labels {
out.Labels[k] = v
}

if in.Description != nil {
out.Annotations = map[string]string{
constants.DescriptionAnnotation: *in.Description,
}
}

return out
}

// UpdateObjectMetadata abstracts away metadata updates e.g. name and description changes.
func UpdateObjectMetadata(out, in metav1.Object) {
out.SetLabels(in.GetLabels())
out.SetAnnotations(in.GetAnnotations())
}

0 comments on commit 5f463af

Please sign in to comment.