mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 18:25:26 +00:00
- ensure OpenAPI definitions always uses strings for setter values - allow the field type to be defined -- integer,boolean,string - format values using yaml 1.1 compatibility
229 lines
6.2 KiB
Go
229 lines
6.2 KiB
Go
// Copyright 2019 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package setters2
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/go-openapi/spec"
|
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
|
"sigs.k8s.io/kustomize/kyaml/openapi"
|
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
)
|
|
|
|
// Set sets resource field values from an OpenAPI setter
|
|
type Set struct {
|
|
// Name is the name of the setter to set on the object. i.e. matches the x-k8s-cli.setter.name
|
|
// of the setter that should have its value applied to fields which reference it.
|
|
Name string
|
|
|
|
// Count is the number of fields that were updated by calling Filter
|
|
Count int
|
|
}
|
|
|
|
// Filter implements Set as a yaml.Filter
|
|
func (s *Set) Filter(object *yaml.RNode) (*yaml.RNode, error) {
|
|
return object, accept(s, object)
|
|
}
|
|
|
|
// visitScalar
|
|
func (s *Set) visitScalar(object *yaml.RNode, p string) error {
|
|
// get the openAPI for this field describing how to apply the setter
|
|
ext, sch, err := getExtFromComment(object)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ext == nil {
|
|
return nil
|
|
}
|
|
|
|
// perform a direct set of the field if it matches
|
|
if s.set(object, ext, sch) {
|
|
s.Count++
|
|
return nil
|
|
}
|
|
|
|
// perform a substitution of the field if it matches
|
|
sub, err := s.substitute(object, ext, sch)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if sub {
|
|
s.Count++
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// substitute updates the value of field from ext if ext contains a substitution that
|
|
// depends on a setter whose name matches s.Name.
|
|
func (s *Set) substitute(field *yaml.RNode, ext *cliExtension, _ *spec.Schema) (bool, error) {
|
|
nameMatch := false
|
|
|
|
// check partial setters to see if they contain the setter as part of a
|
|
// substitution
|
|
if ext.Substitution == nil {
|
|
return false, nil
|
|
}
|
|
|
|
p := ext.Substitution.Pattern
|
|
|
|
// substitute each setter into the pattern to get the new value
|
|
for _, v := range ext.Substitution.Values {
|
|
if v.Ref == "" {
|
|
return false, errors.Errorf(
|
|
"missing reference on substitution " + ext.Substitution.Name)
|
|
}
|
|
ref, err := spec.NewRef(v.Ref)
|
|
if err != nil {
|
|
return false, errors.Wrap(err)
|
|
}
|
|
setter, err := openapi.Resolve(&ref) // resolve the setter to its openAPI def
|
|
if err != nil {
|
|
return false, errors.Wrap(err)
|
|
}
|
|
subSetter, err := getExtFromSchema(setter) // parse the extension out of the openAPI
|
|
if err != nil {
|
|
return false, errors.Wrap(err)
|
|
}
|
|
|
|
if val, found := subSetter.Setter.EnumValues[subSetter.Setter.Value]; found {
|
|
// the setter has an enum-map. we should replace the marker with the
|
|
// enum value looked up from the map rather than the enum key
|
|
p = strings.ReplaceAll(p, v.Marker, val)
|
|
} else {
|
|
// substitute the setters current value into the substitution pattern
|
|
p = strings.ReplaceAll(p, v.Marker, subSetter.Setter.Value)
|
|
}
|
|
|
|
if subSetter.Setter.Name == s.Name {
|
|
// the substitution depends on the specified setter
|
|
nameMatch = true
|
|
}
|
|
}
|
|
if !nameMatch {
|
|
// doesn't depend on the setter, don't modify its value
|
|
return false, nil
|
|
}
|
|
|
|
// TODO(pwittrock): validate the field value
|
|
|
|
field.YNode().Value = p
|
|
|
|
// substitutions are always strings
|
|
field.YNode().Tag = "!!str"
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// set applies the value from ext to field if its name matches s.Name
|
|
func (s *Set) set(field *yaml.RNode, ext *cliExtension, sch *spec.Schema) bool {
|
|
// check full setter
|
|
if ext.Setter == nil || ext.Setter.Name != s.Name {
|
|
return false
|
|
}
|
|
|
|
// TODO(pwittrock): validate the field value
|
|
|
|
if val, found := ext.Setter.EnumValues[ext.Setter.Value]; found {
|
|
// the setter has an enum-map. we should replace the marker with the
|
|
// enum value looked up from the map rather than the enum key
|
|
field.YNode().Value = val
|
|
return true
|
|
}
|
|
|
|
// this has a full setter, set its value
|
|
field.YNode().Value = ext.Setter.Value
|
|
|
|
// format the node so it is quoted if it is a string
|
|
yaml.FormatNonStringStyle(field.YNode(), *sch)
|
|
return true
|
|
}
|
|
|
|
// SetOpenAPI updates a setter value
|
|
type SetOpenAPI struct {
|
|
// Name is the name of the setter to add
|
|
Name string `yaml:"name"`
|
|
// Value is the current value of the setter
|
|
Value string `yaml:"value"`
|
|
|
|
Description string `yaml:"description"`
|
|
|
|
SetBy string `yaml:"setBy"`
|
|
}
|
|
|
|
// UpdateFile updates the OpenAPI definitions in a file with the given setter value.
|
|
func (s SetOpenAPI) UpdateFile(path string) error {
|
|
return yaml.UpdateFile(s, path)
|
|
}
|
|
|
|
func (s SetOpenAPI) Filter(object *yaml.RNode) (*yaml.RNode, error) {
|
|
key := SetterDefinitionPrefix + s.Name
|
|
def, err := object.Pipe(yaml.Lookup(
|
|
"openAPI", "definitions", key, "x-k8s-cli", "setter"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if def == nil {
|
|
return nil, errors.Errorf("no setter %s found", s.Name)
|
|
}
|
|
|
|
// if the setter contains an enumValues map, then ensure the set value appears
|
|
// as a key in the map
|
|
if values, err := def.Pipe(yaml.Lookup("enumValues")); err != nil {
|
|
// error looking up the enumValues
|
|
return nil, err
|
|
} else if values != nil {
|
|
// contains enumValues map -- validate the set value against the map entries
|
|
|
|
// get the enumValues keys
|
|
fields, err := values.Fields()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// search for the user provided value in the set of allowed values
|
|
var match bool
|
|
for i := range fields {
|
|
if fields[i] == s.Value {
|
|
// found a match, we are good
|
|
match = true
|
|
break
|
|
}
|
|
}
|
|
if !match {
|
|
// no match found -- provide an informative error to the user
|
|
return nil, errors.Errorf("%s does not match the possible values for %s: [%s]",
|
|
s.Value, s.Name, strings.Join(fields, ","))
|
|
}
|
|
}
|
|
|
|
v := yaml.NewScalarRNode(s.Value)
|
|
// values are always represented as strings the OpenAPI
|
|
// since the are unmarshalled into strings. Use double quote style to
|
|
// ensure this consistently.
|
|
v.YNode().Tag = "!!str"
|
|
v.YNode().Style = yaml.DoubleQuotedStyle
|
|
|
|
if err := def.PipeE(&yaml.FieldSetter{Name: "value", Value: v}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := def.PipeE(&yaml.FieldSetter{Name: "setBy", StringValue: s.SetBy}); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if s.Description != "" {
|
|
d, err := object.Pipe(yaml.LookupCreate(
|
|
yaml.MappingNode, "openAPI", "definitions", key))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := d.PipeE(&yaml.FieldSetter{Name: "description", StringValue: s.Description}); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return object, nil
|
|
}
|