Skip to content
Open
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
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