1/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package parse
18
19import (
20	"fmt"
21	"strings"
22
23	"k8s.io/kube-openapi/pkg/util/proto"
24)
25
26// Contains functions for casting openapi interfaces to their underlying types
27
28// getSchemaType returns the string type of the schema - e.g. array, primitive, map, kind, reference
29func getSchemaType(schema proto.Schema) string {
30	if schema == nil {
31		return ""
32	}
33	visitor := &baseSchemaVisitor{}
34	schema.Accept(visitor)
35	return visitor.Kind
36}
37
38// getKind converts schema to an *proto.Kind object
39func getKind(schema proto.Schema) (*proto.Kind, error) {
40	if schema == nil {
41		return nil, nil
42	}
43	visitor := &kindSchemaVisitor{}
44	schema.Accept(visitor)
45	return visitor.Result, visitor.Err
46}
47
48// getArray converts schema to an *proto.Array object
49func getArray(schema proto.Schema) (*proto.Array, error) {
50	if schema == nil {
51		return nil, nil
52	}
53	visitor := &arraySchemaVisitor{}
54	schema.Accept(visitor)
55	return visitor.Result, visitor.Err
56}
57
58// getMap converts schema to an *proto.Map object
59func getMap(schema proto.Schema) (*proto.Map, error) {
60	if schema == nil {
61		return nil, nil
62	}
63	visitor := &mapSchemaVisitor{}
64	schema.Accept(visitor)
65	return visitor.Result, visitor.Err
66}
67
68// getPrimitive converts schema to an *proto.Primitive object
69func getPrimitive(schema proto.Schema) (*proto.Primitive, error) {
70	if schema == nil {
71		return nil, nil
72	}
73	visitor := &primitiveSchemaVisitor{}
74	schema.Accept(visitor)
75	return visitor.Result, visitor.Err
76}
77
78type baseSchemaVisitor struct {
79	Err  error
80	Kind string
81}
82
83// VisitArray implements openapi
84func (v *baseSchemaVisitor) VisitArray(array *proto.Array) {
85	v.Kind = "array"
86	v.Err = fmt.Errorf("Array type not expected")
87}
88
89// MergeMap implements openapi
90func (v *baseSchemaVisitor) VisitMap(*proto.Map) {
91	v.Kind = "map"
92	v.Err = fmt.Errorf("Map type not expected")
93}
94
95// MergePrimitive implements openapi
96func (v *baseSchemaVisitor) VisitPrimitive(*proto.Primitive) {
97	v.Kind = "primitive"
98	v.Err = fmt.Errorf("Primitive type not expected")
99}
100
101// VisitKind implements openapi
102func (v *baseSchemaVisitor) VisitKind(*proto.Kind) {
103	v.Kind = "kind"
104	v.Err = fmt.Errorf("Kind type not expected")
105}
106
107// VisitReference implements openapi
108func (v *baseSchemaVisitor) VisitReference(reference proto.Reference) {
109	v.Kind = "reference"
110	v.Err = fmt.Errorf("Reference type not expected")
111}
112
113type kindSchemaVisitor struct {
114	baseSchemaVisitor
115	Result *proto.Kind
116}
117
118// VisitKind implements openapi
119func (v *kindSchemaVisitor) VisitKind(result *proto.Kind) {
120	v.Result = result
121	v.Kind = "kind"
122}
123
124// VisitReference implements openapi
125func (v *kindSchemaVisitor) VisitReference(reference proto.Reference) {
126	reference.SubSchema().Accept(v)
127	if v.Err == nil {
128		v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
129	}
130}
131
132func copyExtensions(field string, from, to map[string]interface{}) error {
133	// Copy extensions from field to type for references
134	for key, val := range from {
135		if curr, found := to[key]; found {
136			// Don't allow the same extension to be defined both on the field and on the type
137			return fmt.Errorf("Cannot override value for extension %s on field %s from %v to %v",
138				key, field, curr, val)
139		}
140		to[key] = val
141	}
142	return nil
143}
144
145type mapSchemaVisitor struct {
146	baseSchemaVisitor
147	Result *proto.Map
148}
149
150// VisitMap implements openapi
151func (v *mapSchemaVisitor) VisitMap(result *proto.Map) {
152	v.Result = result
153	v.Kind = "map"
154}
155
156// VisitReference implements openapi
157func (v *mapSchemaVisitor) VisitReference(reference proto.Reference) {
158	reference.SubSchema().Accept(v)
159	if v.Err == nil {
160		v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
161	}
162}
163
164type arraySchemaVisitor struct {
165	baseSchemaVisitor
166	Result *proto.Array
167}
168
169// VisitArray implements openapi
170func (v *arraySchemaVisitor) VisitArray(result *proto.Array) {
171	v.Result = result
172	v.Kind = "array"
173	v.Err = copySubElementPatchStrategy(result.Path.String(), result.GetExtensions(), result.SubType.GetExtensions())
174}
175
176// copySubElementPatchStrategy copies the strategies to subelements to the subtype
177// e.g. PodTemplate.Volumes is a []Volume with "x-kubernetes-patch-strategy": "merge,retainKeys"
178// the "retainKeys" strategy applies to merging Volumes, and must be copied to the sub element
179func copySubElementPatchStrategy(field string, from, to map[string]interface{}) error {
180	// Check if the parent has a patch strategy extension
181	if ext, found := from["x-kubernetes-patch-strategy"]; found {
182		strategy, ok := ext.(string)
183		if !ok {
184			return fmt.Errorf("Expected string value for x-kubernetes-patch-strategy on %s, was %T",
185				field, ext)
186		}
187		// Check of the parent patch strategy has a sub patch strategy, and if so copy to the sub type
188		if strings.Contains(strategy, ",") {
189			strategies := strings.Split(strategy, ",")
190			if len(strategies) != 2 {
191				// Only 1 sub strategy is supported
192				return fmt.Errorf(
193					"Expected between 0 and 2 elements for x-kubernetes-patch-merge-strategy by got %v",
194					strategies)
195			}
196			to["x-kubernetes-patch-strategy"] = strategies[1]
197		}
198	}
199	return nil
200}
201
202// VisitReference implements openapi
203func (v *arraySchemaVisitor) VisitReference(reference proto.Reference) {
204	reference.SubSchema().Accept(v)
205	if v.Err == nil {
206		v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
207	}
208}
209
210type primitiveSchemaVisitor struct {
211	baseSchemaVisitor
212	Result *proto.Primitive
213}
214
215// VisitPrimitive implements openapi
216func (v *primitiveSchemaVisitor) VisitPrimitive(result *proto.Primitive) {
217	v.Result = result
218	v.Kind = "primitive"
219}
220
221// VisitReference implements openapi
222func (v *primitiveSchemaVisitor) VisitReference(reference proto.Reference) {
223	reference.SubSchema().Accept(v)
224	if v.Err == nil {
225		v.Err = copyExtensions(reference.GetPath().String(), reference.GetExtensions(), v.Result.Extensions)
226	}
227}
228