From 5ef3516780fe26f53eb2b25260b3cc1bb6c55a13 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Wed, 23 Aug 2017 14:11:16 -0700 Subject: [PATCH] openapi: Change reference to be first-class References in the openapi are currently completely hidden from the model, and just passed through as we walk the tree. The problem is that they can have a different description and more importantly, different extensions. Change them to be first-class citizen, and fully part of the model. It means that visitors have to implement one more function and decide if something specific should be done with references. Validation is updated to just completely ignore them and passthrough (like it was done before). --- pkg/kubectl/cmd/util/openapi/document.go | 40 +++++++------------ pkg/kubectl/cmd/util/openapi/openapi.go | 10 +++++ pkg/kubectl/cmd/util/openapi/openapi_test.go | 34 ++++++++-------- .../cmd/util/openapi/validation/types.go | 15 +++++++ 4 files changed, 57 insertions(+), 42 deletions(-) diff --git a/pkg/kubectl/cmd/util/openapi/document.go b/pkg/kubectl/cmd/util/openapi/document.go index 23e83b3dda1..6b2f6782b94 100644 --- a/pkg/kubectl/cmd/util/openapi/document.go +++ b/pkg/kubectl/cmd/util/openapi/document.go @@ -164,8 +164,9 @@ func (d *Definitions) parseReference(s *openapi_v2.Schema, path *Path) (Schema, if _, ok := d.models[reference]; !ok { return nil, newSchemaError(path, "unknown model in reference: %q", reference) } - return &Reference{ - Reference: reference, + return &Ref{ + BaseSchema: d.parseBaseSchema(s, path), + reference: reference, definitions: d, }, nil } @@ -303,38 +304,27 @@ func (d *Definitions) LookupResource(gvk schema.GroupVersionKind) Schema { return model } -// SchemaReference doesn't match a specific type. It's mostly a -// pass-through type. -type Reference struct { - Reference string +type Ref struct { + BaseSchema + reference string definitions *Definitions } -var _ Schema = &Reference{} +var _ Reference = &Ref{} -func (r *Reference) GetSubSchema() Schema { - return r.definitions.models[r.Reference] +func (r *Ref) Reference() string { + return r.reference } -func (r *Reference) Accept(s SchemaVisitor) { - r.GetSubSchema().Accept(s) +func (r *Ref) SubSchema() Schema { + return r.definitions.models[r.reference] } -func (r *Reference) GetDescription() string { - return r.GetSubSchema().GetDescription() +func (r *Ref) Accept(v SchemaVisitor) { + v.VisitReference(r) } -func (r *Reference) GetExtensions() map[string]interface{} { - return r.GetSubSchema().GetExtensions() -} - -func (*Reference) GetPath() *Path { - // Reference never has a path, because it can be referenced from - // multiple locations. - return &Path{} -} - -func (r *Reference) GetName() string { - return r.Reference +func (r *Ref) GetName() string { + return fmt.Sprintf("Reference to %q", r.reference) } diff --git a/pkg/kubectl/cmd/util/openapi/openapi.go b/pkg/kubectl/cmd/util/openapi/openapi.go index 81f1f0eae5b..8e6cf0639f4 100644 --- a/pkg/kubectl/cmd/util/openapi/openapi.go +++ b/pkg/kubectl/cmd/util/openapi/openapi.go @@ -49,11 +49,13 @@ type Resources interface { // - Map is a map of string to one and only one given subtype // - Primitive can be string, integer, number and boolean. // - Kind is an object with specific fields mapping to specific types. +// - Reference is a link to another definition. type SchemaVisitor interface { VisitArray(*Array) VisitMap(*Map) VisitPrimitive(*Primitive) VisitKind(*Kind) + VisitReference(Reference) } // Schema is the base definition of an openapi type. @@ -219,3 +221,11 @@ func (p *Primitive) GetName() string { } return fmt.Sprintf("%s (%s)", p.Type, p.Format) } + +// Reference implementation depends on the type of document. +type Reference interface { + Schema + + Reference() string + SubSchema() Schema +} diff --git a/pkg/kubectl/cmd/util/openapi/openapi_test.go b/pkg/kubectl/cmd/util/openapi/openapi_test.go index e2eb2fa0c0d..93736c93ca7 100644 --- a/pkg/kubectl/cmd/util/openapi/openapi_test.go +++ b/pkg/kubectl/cmd/util/openapi/openapi_test.go @@ -78,20 +78,20 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() { It("should have a metadata key of type Reference", func() { Expect(deployment.Fields).To(HaveKey("metadata")) - key := deployment.Fields["metadata"].(*openapi.Reference) + key := deployment.Fields["metadata"].(openapi.Reference) Expect(key).ToNot(BeNil()) - Expect(key.Reference).To(Equal("io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta")) - subSchema := key.GetSubSchema().(*openapi.Kind) + Expect(key.Reference()).To(Equal("io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta")) + subSchema := key.SubSchema().(*openapi.Kind) Expect(subSchema).ToNot(BeNil()) }) var status *openapi.Kind It("should have a status key of type Reference", func() { Expect(deployment.Fields).To(HaveKey("status")) - key := deployment.Fields["status"].(*openapi.Reference) + key := deployment.Fields["status"].(openapi.Reference) Expect(key).ToNot(BeNil()) - Expect(key.Reference).To(Equal("io.k8s.api.apps.v1beta1.DeploymentStatus")) - status = key.GetSubSchema().(*openapi.Kind) + Expect(key.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentStatus")) + status = key.SubSchema().(*openapi.Kind) Expect(status).ToNot(BeNil()) }) @@ -106,22 +106,22 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() { Expect(status.Fields).To(HaveKey("conditions")) conditions := status.Fields["conditions"].(*openapi.Array) Expect(conditions).ToNot(BeNil()) - Expect(conditions.GetName()).To(Equal("Array of io.k8s.api.apps.v1beta1.DeploymentCondition")) + Expect(conditions.GetName()).To(Equal(`Array of Reference to "io.k8s.api.apps.v1beta1.DeploymentCondition"`)) Expect(conditions.GetExtensions()).To(Equal(map[string]interface{}{ "x-kubernetes-patch-merge-key": "type", "x-kubernetes-patch-strategy": "merge", })) - condition := conditions.SubType.(*openapi.Reference) - Expect(condition.Reference).To(Equal("io.k8s.api.apps.v1beta1.DeploymentCondition")) + condition := conditions.SubType.(openapi.Reference) + Expect(condition.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentCondition")) }) var spec *openapi.Kind It("should have a spec key of type Reference", func() { Expect(deployment.Fields).To(HaveKey("spec")) - key := deployment.Fields["spec"].(*openapi.Reference) + key := deployment.Fields["spec"].(openapi.Reference) Expect(key).ToNot(BeNil()) - Expect(key.Reference).To(Equal("io.k8s.api.apps.v1beta1.DeploymentSpec")) - spec = key.GetSubSchema().(*openapi.Kind) + Expect(key.Reference()).To(Equal("io.k8s.api.apps.v1beta1.DeploymentSpec")) + spec = key.SubSchema().(*openapi.Kind) Expect(spec).ToNot(BeNil()) }) @@ -132,9 +132,9 @@ var _ = Describe("Reading apps/v1beta1/Deployment from openAPIData", func() { It("should have a spec with a PodTemplateSpec sub-field", func() { Expect(spec.Fields).To(HaveKey("template")) - key := spec.Fields["template"].(*openapi.Reference) + key := spec.Fields["template"].(openapi.Reference) Expect(key).ToNot(BeNil()) - Expect(key.Reference).To(Equal("io.k8s.api.core.v1.PodTemplateSpec")) + Expect(key.Reference()).To(Equal("io.k8s.api.core.v1.PodTemplateSpec")) }) }) @@ -164,10 +164,10 @@ var _ = Describe("Reading authorization.k8s.io/v1/SubjectAccessReview from openA sar := schema.(*openapi.Kind) Expect(sar).ToNot(BeNil()) Expect(sar.Fields).To(HaveKey("spec")) - specRef := sar.Fields["spec"].(*openapi.Reference) + specRef := sar.Fields["spec"].(openapi.Reference) Expect(specRef).ToNot(BeNil()) - Expect(specRef.Reference).To(Equal("io.k8s.api.authorization.v1.SubjectAccessReviewSpec")) - sarspec = specRef.GetSubSchema().(*openapi.Kind) + Expect(specRef.Reference()).To(Equal("io.k8s.api.authorization.v1.SubjectAccessReviewSpec")) + sarspec = specRef.SubSchema().(*openapi.Kind) Expect(sarspec).ToNot(BeNil()) }) diff --git a/pkg/kubectl/cmd/util/openapi/validation/types.go b/pkg/kubectl/cmd/util/openapi/validation/types.go index 335444c6c24..2c16c2b16fc 100644 --- a/pkg/kubectl/cmd/util/openapi/validation/types.go +++ b/pkg/kubectl/cmd/util/openapi/validation/types.go @@ -127,6 +127,11 @@ func (item *mapItem) VisitKind(schema *openapi.Kind) { } } +func (item *mapItem) VisitReference(schema openapi.Reference) { + // passthrough + schema.SubSchema().Accept(item) +} + // arrayItem represents a yaml array. type arrayItem struct { baseItem @@ -165,6 +170,11 @@ func (item *arrayItem) VisitKind(schema *openapi.Kind) { item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "array", Actual: "map"}) } +func (item *arrayItem) VisitReference(schema openapi.Reference) { + // passthrough + schema.SubSchema().Accept(item) +} + // primitiveItem represents a yaml value. type primitiveItem struct { baseItem @@ -216,6 +226,11 @@ func (item *primitiveItem) VisitKind(schema *openapi.Kind) { item.AddValidationError(InvalidTypeError{Path: schema.GetPath().String(), Expected: "map", Actual: item.Kind}) } +func (item *primitiveItem) VisitReference(schema openapi.Reference) { + // passthrough + schema.SubSchema().Accept(item) +} + // itemFactory creates the relevant item type/visitor based on the current yaml type. func itemFactory(path openapi.Path, v interface{}) (ValidationItem, error) { // We need to special case for no-type fields in yaml (e.g. empty item in list)