Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions internal/app/apiserverapp/apiserverapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,26 @@ func getOpenAPIDefinitions(_ openapicommon.ReferenceCallback) map[string]openapi
templateDefinitionName := openapiutil.GetCanonicalTypeName(&aggregationv1alpha1.CoderTemplate{})
templateListDefinitionName := openapiutil.GetCanonicalTypeName(&aggregationv1alpha1.CoderTemplateList{})

groupVersionKindExtension := func(kind string) spec.VendorExtensible {
return spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-group-version-kind": []interface{}{
map[string]interface{}{
"group": aggregationv1alpha1.SchemeGroupVersion.Group,
"version": aggregationv1alpha1.SchemeGroupVersion.Version,
"kind": kind,
},
},
},
}
}

boolSchema := spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"boolean"}}}
dateTimeSchema := spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"string"}, Format: "date-time"}}
int64Schema := spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"integer"}, Format: "int64"}}
stringSchema := spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"string"}}}
objectMetaSchema := spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}}}
listMetaSchema := spec.Schema{SchemaProps: spec.SchemaProps{Type: []string{"object"}}}
filesSchema := spec.Schema{
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
Expand All @@ -365,9 +381,13 @@ func getOpenAPIDefinitions(_ openapicommon.ReferenceCallback) map[string]openapi
}

workspaceSchema := spec.Schema{
VendorExtensible: groupVersionKindExtension("CoderWorkspace"),
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"apiVersion": stringSchema,
"kind": stringSchema,
"metadata": objectMetaSchema,
"spec": {
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Expand Down Expand Up @@ -401,9 +421,13 @@ func getOpenAPIDefinitions(_ openapicommon.ReferenceCallback) map[string]openapi
}

templateSchema := spec.Schema{
VendorExtensible: groupVersionKindExtension("CoderTemplate"),
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"apiVersion": stringSchema,
"kind": stringSchema,
"metadata": objectMetaSchema,
"spec": {
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Expand Down Expand Up @@ -436,9 +460,13 @@ func getOpenAPIDefinitions(_ openapicommon.ReferenceCallback) map[string]openapi
}

workspaceListSchema := spec.Schema{
VendorExtensible: groupVersionKindExtension("CoderWorkspaceList"),
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"apiVersion": stringSchema,
"kind": stringSchema,
"metadata": listMetaSchema,
"items": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Expand All @@ -450,9 +478,13 @@ func getOpenAPIDefinitions(_ openapicommon.ReferenceCallback) map[string]openapi
}

templateListSchema := spec.Schema{
VendorExtensible: groupVersionKindExtension("CoderTemplateList"),
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"apiVersion": stringSchema,
"kind": stringSchema,
"metadata": listMetaSchema,
"items": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Expand Down
115 changes: 115 additions & 0 deletions internal/app/apiserverapp/apiserverapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import (
"time"

apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/util/managedfields"
genericoptions "k8s.io/apiserver/pkg/server/options"
openapiutil "k8s.io/kube-openapi/pkg/util"
"k8s.io/kube-openapi/pkg/validation/spec"

aggregationv1alpha1 "github.com/coder/coder-k8s/api/aggregation/v1alpha1"
coderhelper "github.com/coder/coder-k8s/internal/aggregated/coder"
Expand Down Expand Up @@ -76,6 +79,109 @@ func TestOpenAPIDefinitionsIncludeTemplateFiles(t *testing.T) {
}
}

func TestOpenAPIDefinitionsIncludeTemplateGVKExtensionAndObjectMetadata(t *testing.T) {
t.Helper()

defs := getOpenAPIDefinitions(nil)
templateDefinitionName := openapiutil.GetCanonicalTypeName(&aggregationv1alpha1.CoderTemplate{})

def, ok := defs[templateDefinitionName]
if !ok {
t.Fatalf("expected OpenAPI definition for %s", templateDefinitionName)
}

for _, propertyName := range []string{"apiVersion", "kind", "metadata", "spec", "status"} {
if _, ok := def.Schema.Properties[propertyName]; !ok {
t.Fatalf("expected template schema to include %q", propertyName)
}
}

gvk := readGVKExtension(t, def.Schema)
if got, want := gvk["group"], aggregationv1alpha1.SchemeGroupVersion.Group; got != want {
t.Fatalf("expected template GVK group %q, got %v", want, got)
}
if got, want := gvk["version"], aggregationv1alpha1.SchemeGroupVersion.Version; got != want {
t.Fatalf("expected template GVK version %q, got %v", want, got)
}
if got, want := gvk["kind"], "CoderTemplate"; got != want {
t.Fatalf("expected template GVK kind %q, got %v", want, got)
}
}

func TestOpenAPIDefinitionsSupportManagedFieldsTypeConversionForTemplate(t *testing.T) {
t.Helper()

defs := getOpenAPIDefinitions(nil)
openAPISpec := make(map[string]*spec.Schema, len(defs))
for definitionName, definition := range defs {
definitionSchema := definition.Schema
openAPISpec[definitionName] = &definitionSchema
}

typeConverter, err := managedfields.NewTypeConverter(openAPISpec, false)
if err != nil {
t.Fatalf("build managed fields type converter from OpenAPI definitions: %v", err)
}
if typeConverter == nil {
t.Fatal("expected managed fields type converter to be non-nil")
}

template := &aggregationv1alpha1.CoderTemplate{
TypeMeta: metav1.TypeMeta{
APIVersion: aggregationv1alpha1.SchemeGroupVersion.String(),
Kind: "CoderTemplate",
},
ObjectMeta: metav1.ObjectMeta{
Name: "default.my-template",
Namespace: "test-ns",
},
Spec: aggregationv1alpha1.CoderTemplateSpec{
Organization: "default",
VersionID: "version-id",
},
}

if _, err := typeConverter.ObjectToTyped(template); err != nil {
t.Fatalf("convert template object to structured-merge typed value: %v", err)
}
}

func readGVKExtension(t *testing.T, schema spec.Schema) map[string]interface{} {
t.Helper()

extension, ok := schema.Extensions["x-kubernetes-group-version-kind"]
if !ok {
t.Fatal("expected x-kubernetes-group-version-kind OpenAPI extension")
}

gvkList, ok := extension.([]interface{})
if !ok {
t.Fatalf("expected GVK extension to be []interface{}, got %T", extension)
}
if len(gvkList) != 1 {
t.Fatalf("expected exactly one GVK entry, got %d", len(gvkList))
}

switch gvk := gvkList[0].(type) {
case map[string]interface{}:
return gvk
case map[interface{}]interface{}:
normalized := make(map[string]interface{}, len(gvk))
for key, value := range gvk {
keyString, ok := key.(string)
if !ok {
t.Fatalf("expected GVK extension map key to be string, got %T", key)
}
normalized[keyString] = value
}
return normalized
default:
t.Fatalf("expected GVK entry to be map, got %T", gvkList[0])
}

return nil
}

func TestInstallAPIGroupRegistersDiscovery(t *testing.T) {
t.Helper()

Expand Down Expand Up @@ -196,6 +302,15 @@ func TestNewRecommendedConfigSetsExtendedRequestTimeout(t *testing.T) {
if got, want := recommendedConfig.RequestTimeout, defaultRequestTimeout; got != want {
t.Fatalf("expected request timeout %s, got %s", want, got)
}
if !recommendedConfig.SkipOpenAPIInstallation {
t.Fatal("expected OpenAPI handler installation to remain disabled until generic definitions are wired")
}
if recommendedConfig.OpenAPIConfig == nil {
t.Fatal("expected non-nil OpenAPI v2 config")
}
if recommendedConfig.OpenAPIV3Config == nil {
t.Fatal("expected non-nil OpenAPI v3 config")
}
}

func TestBuildClientProviderDefersMissingCoderConfigAsServiceUnavailable(t *testing.T) {
Expand Down
Loading