mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 18:25:26 +00:00
Merge pull request #2496 from phanimarupaka/SchemaFileToOpenAPI
Add schema-path flag to input openAPI for setters
This commit is contained in:
@@ -48,6 +48,9 @@ func NewCreateSetterRunner(parent string) *CreateSetterRunner {
|
||||
set.Flags().MarkHidden("partial")
|
||||
set.Flags().StringVar(&setterVersion, "version", "",
|
||||
"use this version of the setter format")
|
||||
set.Flags().StringVar(&r.CreateSetter.SchemaPath, "schema-path", "",
|
||||
`openAPI schema file path for setter constraints -- file content `+
|
||||
`e.g. {"type": "string", "maxLength": 15, "enum": ["allowedValue1", "allowedValue2"]}`)
|
||||
set.Flags().MarkHidden("version")
|
||||
fixDocs(parent, set)
|
||||
r.Command = set
|
||||
|
||||
@@ -21,6 +21,7 @@ func TestCreateSetterCommand(t *testing.T) {
|
||||
name string
|
||||
input string
|
||||
args []string
|
||||
schema string
|
||||
out string
|
||||
inputOpenAPI string
|
||||
expectedOpenAPI string
|
||||
@@ -85,6 +86,88 @@ openAPI:
|
||||
`,
|
||||
err: "substitution with name my-image already exists, substitution and setter can't have same name",
|
||||
},
|
||||
|
||||
{
|
||||
name: "add replicas with schema",
|
||||
args: []string{"replicas", "3", "--description", "hello world", "--set-by", "me"},
|
||||
schema: `{"maximum": 10, "type": "integer"}`,
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
spec:
|
||||
replicas: 3
|
||||
`,
|
||||
inputOpenAPI: `
|
||||
apiVersion: v1alpha1
|
||||
kind: Example
|
||||
`,
|
||||
expectedOpenAPI: `
|
||||
apiVersion: v1alpha1
|
||||
kind: Example
|
||||
openAPI:
|
||||
definitions:
|
||||
io.k8s.cli.setters.replicas:
|
||||
maximum: 10
|
||||
type: integer
|
||||
description: hello world
|
||||
x-k8s-cli:
|
||||
setter:
|
||||
name: replicas
|
||||
value: "3"
|
||||
setBy: me
|
||||
`,
|
||||
expectedResources: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx-deployment
|
||||
spec:
|
||||
replicas: 3 # {"$ref":"#/definitions/io.k8s.cli.setters.replicas"}
|
||||
`,
|
||||
},
|
||||
|
||||
{
|
||||
name: "add replicas with schema list values",
|
||||
args: []string{"list", "a", "--description", "hello world", "--set-by", "me", "--type", "array"},
|
||||
schema: `{"maxItems": 2, "type": "array", "items": {"type": "string"}}`,
|
||||
input: `
|
||||
apiVersion: example.com/v1beta1
|
||||
kind: Example
|
||||
spec:
|
||||
list:
|
||||
- "a"
|
||||
`,
|
||||
inputOpenAPI: `
|
||||
apiVersion: v1alpha1
|
||||
kind: Example
|
||||
`,
|
||||
expectedOpenAPI: `
|
||||
apiVersion: v1alpha1
|
||||
kind: Example
|
||||
openAPI:
|
||||
definitions:
|
||||
io.k8s.cli.setters.list:
|
||||
items:
|
||||
type: string
|
||||
maxItems: 2
|
||||
type: array
|
||||
description: hello world
|
||||
x-k8s-cli:
|
||||
setter:
|
||||
name: list
|
||||
value: a
|
||||
setBy: me
|
||||
`,
|
||||
expectedResources: `
|
||||
apiVersion: example.com/v1beta1
|
||||
kind: Example
|
||||
spec:
|
||||
list:
|
||||
- "a" # {"$ref":"#/definitions/io.k8s.cli.setters.list"}
|
||||
`,
|
||||
},
|
||||
}
|
||||
for i := range tests {
|
||||
test := tests[i]
|
||||
@@ -98,10 +181,27 @@ openAPI:
|
||||
t.FailNow()
|
||||
}
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
err = ioutil.WriteFile(f.Name(), []byte(test.inputOpenAPI), 0600)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
if test.schema != "" {
|
||||
sch, err := ioutil.TempFile("", "schema.json")
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
defer os.Remove(sch.Name())
|
||||
|
||||
err = ioutil.WriteFile(sch.Name(), []byte(test.schema), 0600)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
test.args = append(test.args, "--schema-path", sch.Name())
|
||||
}
|
||||
|
||||
old := ext.GetOpenAPIFile
|
||||
defer func() { ext.GetOpenAPIFile = old }()
|
||||
ext.GetOpenAPIFile = func(args []string) (s string, err error) {
|
||||
|
||||
@@ -116,6 +116,9 @@ type SetterDefinition struct {
|
||||
// Type is the type of the setter value.
|
||||
Type string `yaml:"type,omitempty"`
|
||||
|
||||
// Schema is the openAPI schema for setter constraints.
|
||||
Schema string `yaml:"schema,omitempty"`
|
||||
|
||||
// EnumValues is a map of possible setter values to actual field values.
|
||||
// If EnumValues is specified, then the value set the by user 1) MUST
|
||||
// be present in the enumValues map as a key, and 2) the map entry value
|
||||
@@ -132,13 +135,33 @@ func (sd SetterDefinition) AddToFile(path string) error {
|
||||
func (sd SetterDefinition) Filter(object *yaml.RNode) (*yaml.RNode, error) {
|
||||
key := SetterDefinitionPrefix + sd.Name
|
||||
|
||||
def, err := object.Pipe(yaml.LookupCreate(
|
||||
yaml.MappingNode, openapi.SupplementaryOpenAPIFieldName, "definitions", key))
|
||||
definitions, err := object.Pipe(yaml.LookupCreate(
|
||||
yaml.MappingNode, openapi.SupplementaryOpenAPIFieldName, "definitions"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setterDef, err := definitions.Pipe(yaml.LookupCreate(yaml.MappingNode, key))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if sd.Schema != "" {
|
||||
schNode, err := yaml.ConvertJSONToYamlNode(sd.Schema)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = definitions.PipeE(yaml.SetField(key, schNode))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// don't write the schema to the extension
|
||||
sd.Schema = ""
|
||||
}
|
||||
|
||||
if sd.Description != "" {
|
||||
err = def.PipeE(yaml.FieldSetter{Name: "description", StringValue: sd.Description})
|
||||
err = setterDef.PipeE(yaml.FieldSetter{Name: "description", StringValue: sd.Description})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -147,7 +170,7 @@ func (sd SetterDefinition) Filter(object *yaml.RNode) (*yaml.RNode, error) {
|
||||
}
|
||||
|
||||
if sd.Type != "" {
|
||||
err = def.PipeE(yaml.FieldSetter{Name: "type", StringValue: sd.Type})
|
||||
err = setterDef.PipeE(yaml.FieldSetter{Name: "type", StringValue: sd.Type})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -155,7 +178,7 @@ func (sd SetterDefinition) Filter(object *yaml.RNode) (*yaml.RNode, error) {
|
||||
sd.Type = ""
|
||||
}
|
||||
|
||||
ext, err := def.Pipe(yaml.LookupCreate(yaml.MappingNode, K8sCliExtensionKey))
|
||||
ext, err := setterDef.Pipe(yaml.LookupCreate(yaml.MappingNode, K8sCliExtensionKey))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
package settersutil
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/openapi"
|
||||
"sigs.k8s.io/kustomize/kyaml/setters2"
|
||||
@@ -21,6 +23,8 @@ type SetterCreator struct {
|
||||
|
||||
Type string
|
||||
|
||||
SchemaPath string
|
||||
|
||||
// FieldName if set will add the OpenAPI reference to fields with this name or path
|
||||
// FieldName may be the full name of the field, full path to the field, or the path suffix.
|
||||
// e.g. all of the following would match spec.template.spec.containers.image --
|
||||
@@ -35,10 +39,14 @@ type SetterCreator struct {
|
||||
}
|
||||
|
||||
func (c SetterCreator) Create(openAPIPath, resourcesPath string) error {
|
||||
schema, err := schemaFromFile(c.SchemaPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Update the OpenAPI definitions to hace the setter
|
||||
sd := setters2.SetterDefinition{
|
||||
Name: c.Name, Value: c.FieldValue, Description: c.Description, SetBy: c.SetBy,
|
||||
Type: c.Type,
|
||||
Type: c.Type, Schema: schema,
|
||||
}
|
||||
if err := sd.AddToFile(openAPIPath); err != nil {
|
||||
return err
|
||||
@@ -62,3 +70,15 @@ func (c SetterCreator) Create(openAPIPath, resourcesPath string) error {
|
||||
Outputs: []kio.Writer{inout},
|
||||
}.Execute()
|
||||
}
|
||||
|
||||
// schemaFromFile reads the contents from schemaPath and returns schema
|
||||
func schemaFromFile(schemaPath string) (string, error) {
|
||||
if schemaPath == "" {
|
||||
return "", nil
|
||||
}
|
||||
sch, err := ioutil.ReadFile(schemaPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(sch), nil
|
||||
}
|
||||
|
||||
@@ -722,6 +722,24 @@ func (rn *RNode) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConvertJSONToYamlNode parses input json string and returns equivalent yaml node
|
||||
func ConvertJSONToYamlNode(jsonStr string) (*RNode, error) {
|
||||
var body map[string]interface{}
|
||||
err := json.Unmarshal([]byte(jsonStr), &body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
yml, err := yaml.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node, err := Parse(string(yml))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// checkKey returns true if all elems have the key
|
||||
func checkKey(key string, elems []*Node) bool {
|
||||
count := 0
|
||||
|
||||
@@ -146,3 +146,23 @@ hello: world
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertJSONToYamlNode(t *testing.T) {
|
||||
inputJSON := `{"type": "string", "maxLength": 15, "enum": ["allowedValue1", "allowedValue2"]}`
|
||||
expected := `enum:
|
||||
- allowedValue1
|
||||
- allowedValue2
|
||||
maxLength: 15
|
||||
type: string
|
||||
`
|
||||
|
||||
node, err := ConvertJSONToYamlNode(inputJSON)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
actual, err := node.String()
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user