Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 1 addition & 3 deletions manifest/openapi/foundry_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ func NewFoundryFromSpecV2(spec []byte) (Foundry, error) {

f := foapiv2{
swagger: &swg,
typeCache: sync.Map{},
gkvIndex: sync.Map{}, //reverse lookup index from GVK to OpenAPI definition IDs
recursionDepth: 50, // arbitrarily large number - a type this deep will likely kill Terraform anyway
gate: sync.Mutex{},
Expand All @@ -57,7 +56,6 @@ type Foundry interface {

type foapiv2 struct {
swagger *openapi2.T
typeCache sync.Map
gkvIndex sync.Map
recursionDepth uint64 // a last resort circuit-breaker for run-away recursion - hitting this will make for a bad day
gate sync.Mutex
Expand Down Expand Up @@ -105,7 +103,7 @@ func (f *foapiv2) getTypeByID(id string, h map[string]string, ap tftypes.Attribu
return nil, fmt.Errorf("failed to resolve schema: %s", err)
}

return getTypeFromSchema(sch, f.recursionDepth, &(f.typeCache), f.swagger.Definitions, ap, h)
return getTypeFromSchema(sch, f.recursionDepth, f.swagger.Definitions, ap, h)
}

// buildGvkIndex builds the reverse lookup index that associates each GVK
Expand Down
79 changes: 60 additions & 19 deletions manifest/openapi/foundry_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package openapi

import (
"encoding/json"
"fmt"
"sync"

Expand All @@ -18,49 +19,89 @@ func NewFoundryFromSpecV3(spec []byte) (Foundry, error) {
if err != nil {
return nil, err
}
return &foapiv3{doc: oapi3}, nil
f := &foapiv3{doc: oapi3}

err = f.buildGvkIndex()
if err != nil {
return nil, fmt.Errorf("failed to build GVK index when creating new foundry: %w", err)
}

return f, nil
}

func SchemaToSpec(key string, crschema map[string]interface{}) map[string]interface{} {
schema := make(map[string]interface{})
func CRDSchemaToSpec(gvk schema.GroupVersionKind, crschema map[string]any) map[string]any {

schema := make(map[string]any)
for k, v := range crschema {
schema[k] = v
}
return map[string]interface{}{
schema["x-kubernetes-group-version-kind"] = []map[string]any{
{
"group": gvk.Group,
"version": gvk.Version,
"kind": gvk.Kind,
},
}
return map[string]any{
"openapi": "3.0",
"info": map[string]interface{}{
"info": map[string]any{
"title": "CRD schema wrapper",
"version": "1.0.0",
},
"paths": map[string]interface{}{},
"components": map[string]interface{}{
"schemas": map[string]interface{}{
key: schema,
"paths": map[string]any{},
"components": map[string]any{
"schemas": map[string]any{
"crd-schema": schema,
},
},
}
}

type foapiv3 struct {
doc *openapi3.T
gate sync.Mutex
typeCache sync.Map
gkvIndex sync.Map
}

func (f *foapiv3) GetTypeByGVK(_ schema.GroupVersionKind) (tftypes.Type, map[string]string, error) {
f.gate.Lock()
defer f.gate.Unlock()

func (f *foapiv3) GetTypeByGVK(gvk schema.GroupVersionKind) (tftypes.Type, map[string]string, error) {
var hints map[string]string = make(map[string]string)
ap := tftypes.AttributePath{}

sref := f.doc.Components.Schemas[""]
// the ID string that OpenAPI uses to identify the resource
// e.g. "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
id, ok := f.gkvIndex.Load(gvk)
if !ok {
return nil, nil, fmt.Errorf("resource not found in OpenAPI index")
}

sref := f.doc.Components.Schemas[id.(string)]
sch, err := resolveSchemaRef(sref, f.doc.Components.Schemas)
if err != nil {
return nil, hints, fmt.Errorf("failed to resolve schema: %s", err)
return nil, hints, fmt.Errorf("failed to resolve schema: %w", err)
}

tftype, err := getTypeFromSchema(sch, 50, &(f.typeCache), f.doc.Components.Schemas, ap, hints)
tftype, err := getTypeFromSchema(sch, 50, f.doc.Components.Schemas, tftypes.AttributePath{}, hints)
return tftype, hints, err
}

// buildGvkIndex builds the reverse lookup index that associates each GVK
// to its corresponding string key in the swagger.Definitions map
func (f *foapiv3) buildGvkIndex() error {
for did, dRef := range f.doc.Components.Schemas {
def, err := resolveSchemaRef(dRef, f.doc.Components.Schemas)
if err != nil {
return err
}
ex, ok := def.Extensions["x-kubernetes-group-version-kind"]
if !ok {
continue
}
gvk := []schema.GroupVersionKind{}
err = json.Unmarshal(([]byte)(ex.(json.RawMessage)), &gvk)
if err != nil {
return fmt.Errorf("failed to unmarshall GVK from OpenAPI schema extention: %w", err)
}
for i := range gvk {
f.gkvIndex.Store(gvk[i], did)
}
}
return nil
}
43 changes: 27 additions & 16 deletions manifest/openapi/foundry_v3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package openapi
import (
"encoding/json"
"testing"

"k8s.io/apimachinery/pkg/runtime/schema"
)

func TestNewFoundryFromSpecV3(t *testing.T) {
Expand All @@ -20,49 +22,58 @@ func TestNewFoundryFromSpecV3(t *testing.T) {
},
"type": "object",
}

spec := SchemaToSpec("com.hashicorp.v1.TestCrd", sampleSchema)
gvk := schema.FromAPIVersionAndKind("hashicorp.com/v1", "TestCrd")
spec := CRDSchemaToSpec(gvk, sampleSchema)
j, err := json.Marshal(spec)
if err != nil {
t.Fatalf("Error: %+v", err)
}

f, err := NewFoundryFromSpecV3(j)
if err != nil {
t.Fatalf("Error: %+v", err)
t.Fatalf("Error creating foundry: %v", err)
}

f3, ok := f.(*foapiv3)
if !ok {
t.Fatal("foundry not of expected type")
}

if f.(*foapiv3).doc == nil {
t.Fail()
if f3.doc == nil {
t.Fatal("no doc")
}
if f.(*foapiv3).doc.Components.Schemas == nil {
t.Fail()
if f3.doc.Components.Schemas == nil {
t.Fatal("no schemas")
}
id, ok := f3.gkvIndex.Load(gvk)
if !ok {
t.Fatal("could not lookup schema id")
}
crd, ok := f.(*foapiv3).doc.Components.Schemas["com.hashicorp.v1.TestCrd"]
crd, ok := f3.doc.Components.Schemas[id.(string)]
if !ok {
t.Fail()
t.Fatal("CRD schema not found")
}
if crd == nil || crd.Value == nil {
t.Fail()
t.Fatal("CRD schema empty")
}
if crd.Value.Type != "object" {
t.Fail()
t.Fatal("CRD type not object")
}
if crd.Value.Properties == nil {
t.Fail()
t.Fatal("CRD missing properties")
}
foo, ok := crd.Value.Properties["foo"]
if !ok {
t.Fail()
t.Fatal("CRD missing property foo")
}
if foo.Value.Type != "string" {
t.Fail()
t.Fatal("CRD property foo not a string")
}
bar, ok := crd.Value.Properties["bar"]
if !ok {
t.Fail()
t.Fatal("CRD missing property bar")
}
if bar.Value.Type != "number" {
t.Fail()
t.Fatal("CRD property bar not a number")
}
}
Loading