Skip to content

Commit

Permalink
convert vars to replacements
Browse files Browse the repository at this point in the history
  • Loading branch information
natasha41575 committed Jun 10, 2021
1 parent 5aa2f53 commit f121e74
Show file tree
Hide file tree
Showing 9 changed files with 1,504 additions and 12 deletions.
2 changes: 1 addition & 1 deletion kustomize/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ See https://sigs.k8s.io/kustomize
completion.NewCommand(),
makeBuildCommand(fSys, stdOut),
edit.NewCmdEdit(
fSys, pvd.GetFieldValidator(), pvd.GetResourceFactory()),
fSys, pvd.GetFieldValidator(), pvd.GetResourceFactory(), stdOut),
create.NewCmdCreate(fSys, pvd.GetResourceFactory()),
version.NewCmdVersion(stdOut),
openapi.NewCmdOpenAPI(stdOut),
Expand Down
7 changes: 5 additions & 2 deletions kustomize/commands/edit/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package edit

import (
"io"

"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/ifc"
Expand All @@ -19,7 +21,8 @@ import (

// NewCmdEdit returns an instance of 'edit' subcommand.
func NewCmdEdit(
fSys filesys.FileSystem, v ifc.Validator, rf *resource.Factory) *cobra.Command {
fSys filesys.FileSystem, v ifc.Validator, rf *resource.Factory,
w io.Writer) *cobra.Command {
c := &cobra.Command{
Use: "edit",
Short: "Edits a kustomization file",
Expand All @@ -46,7 +49,7 @@ func NewCmdEdit(
fSys,
kv.NewLoader(loader.NewFileLoaderAtCwd(fSys), v),
v),
fix.NewCmdFix(fSys),
fix.NewCmdFix(fSys, w),
remove.NewCmdRemove(fSys, v),
listbuiltin.NewCmdListBuiltinPlugin(),
)
Expand Down
345 changes: 345 additions & 0 deletions kustomize/commands/edit/fix/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,345 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package fix

import (
"bytes"
"fmt"
"path"
"strconv"
"strings"

"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/resid"
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/yaml"
)

func ConvertVarsToReplacements(fSys filesys.FileSystem, k *types.Kustomization) error {
if k.Vars == nil {
return nil
}

k.Resources = append(k.Resources, k.Bases...)
k.Replacements = []types.ReplacementField{}

files, err := filesTouchedByKustomize(k, "", fSys)
if err != nil {
return err
}

for _, v := range k.Vars {
repl := &types.Replacement{}
if err := addTargets(repl, v.Name, files, fSys); err != nil {
return err
}
copySourceFromVars(repl, v)
if err := setPlaceholderValue(v.Name, files, fSys); err != nil {
return err
}
k.Replacements = append(k.Replacements, types.ReplacementField{Replacement: *repl})
}
k.Vars = nil
return nil
}

var patchTarget = make(map[string]types.Patch)

func filesTouchedByKustomize(k *types.Kustomization, filepath string, fSys filesys.FileSystem) ([]string, error) {
var result []string
for _, r := range k.Resources {
// first, try to read resource as a base/directory
files, err := fSys.ReadDir(r)
if err == nil && len(files) > 0 {
for _, file := range files {
if !stringInSlice(file, []string{
"kustomization.yaml",
"kustomization.yml",
"Kustomization",
}) {
continue
}

b, err := fSys.ReadFile(path.Join(r, file))
if err != nil {
continue
}

subKt := &types.Kustomization{}
if err := yaml.Unmarshal(b, subKt); err != nil {
return nil, err
}
paths, err := filesTouchedByKustomize(subKt, r, fSys)
if err != nil {
return nil, err
}
result = append(result, paths...)
}

}
// read the resource as a file
result = append(result, path.Join(filepath, r))

}

// aggregate all of the paths from the `patches` field
for _, p := range k.Patches {
if p.Path != "" {
patchPath := path.Join(filepath, p.Path)
result = append(result, patchPath)
patchTarget[patchPath] = p
}
}
return result, nil
}

func copySourceFromVars(repl *types.Replacement, v types.Var) {
repl.Source = &types.SourceSelector{}
apiVersion := v.ObjRef.APIVersion
group, version := resid.ParseGroupVersion(apiVersion)
repl.Source.Gvk.Group = group
repl.Source.Gvk.Version = version
repl.Source.Gvk.Kind = v.ObjRef.Kind
repl.Source.Name = v.ObjRef.Name
repl.Source.Namespace = v.ObjRef.Namespace
repl.Source.FieldPath = v.FieldRef.FieldPath
}

func addTargets(repl *types.Replacement, varName string, files []string, fSys filesys.FileSystem) error {
for _, file := range files {
nodes, err := getNodesFromFile(file, fSys)
if err != nil {
continue
}
for _, n := range nodes {
fieldPaths, options, err := findVarName(n, varName, []string{})
if err != nil {
return fmt.Errorf("error with %s: %s", file, err.Error())
}
targets, err := constructTargets(file, n, fieldPaths, options)
if err != nil {
return err
}
repl.Targets = append(repl.Targets, targets...)
}
}
return nil
}

func getNodesFromFile(fileName string, fSys filesys.FileSystem) ([]*kyaml.RNode, error) {
b, err := fSys.ReadFile(fileName)
if err != nil {
return nil, err
}
out := &bytes.Buffer{}
r := kio.ByteReadWriter{
Reader: bytes.NewBufferString(string(b)),
Writer: out,
KeepReaderAnnotations: true,
OmitReaderAnnotations: true,
}
return r.Read()
}

func findVarName(node *kyaml.RNode, varName string, path []string) ([]string, []*types.FieldOptions, error) {
var fieldPaths []string
var options []*types.FieldOptions

switch node.YNode().Kind {

case kyaml.SequenceNode:
elements, err := node.Elements()
if err != nil {
return nil, nil, err
}
for i := range elements {
nextPathItem := strings.TrimSpace(strconv.Itoa(i))
fieldPathsToAdd, optionsToAdd, err := findVarName(elements[i],
varName, append(path, nextPathItem))
if err != nil {
return nil, nil, err
}
fieldPaths = append(fieldPaths, fieldPathsToAdd...)
options = append(options, optionsToAdd...)
}

case kyaml.MappingNode:
err := node.VisitFields(func(n *kyaml.MapNode) error {
nextPathItem := strings.TrimSpace(n.Key.MustString())
fieldPathsToAdd, optionsToAdd, err := findVarName(n.Value.Copy(),
varName, append(path, nextPathItem))
if err != nil {
return err
}
fieldPaths = append(fieldPaths, fieldPathsToAdd...)
options = append(options, optionsToAdd...)
return nil
})
if err != nil {
return nil, nil, err
}

case kyaml.ScalarNode:
value := node.YNode().Value
varString := fmt.Sprintf("$(%s)", varName)
if strings.Contains(value, varString) {
fieldPaths = append(fieldPaths, strings.Join(path, "."))
optionsToAdd, err := constructFieldOptions(value, varString)
if err != nil {
return nil, nil, err
}
options = append(options, optionsToAdd...)
}
}

return fieldPaths, options, nil
}

func constructFieldOptions(value string, varString string) ([]*types.FieldOptions, error) {
if value == varString {
return []*types.FieldOptions{{}}, nil
}

var delimiter string
var index int
i := strings.Index(value, varString)

// all array accesses here are safe because we know value != varString and
// that value contains varString, so len(value) > len(varString)
switch {
case i == 0: // prefix
delimiter = string(value[len(varString)])
index = 0
case (i + len(varString)) >= len(value): // suffix
delimiter = string(value[i-1])
index = len(strings.Split(value, delimiter)) - 1
default: // in the middle somewhere
pre := string(value[i-1])
post := string(value[i+len(varString)])
if pre != post {
return nil, fmt.Errorf("cannot convert all vars to replacements; %s is not delimited", varString)
}
delimiter = pre
index = indexOf(varString, strings.Split(value, delimiter))
if index == -1 {
// this should never happen
return nil, fmt.Errorf("internal error: could not get index of var %s", varString)
}
}
return []*types.FieldOptions{{Delimiter: delimiter, Index: index}}, nil
}

func constructTargets(file string, node *kyaml.RNode, fieldPaths []string,
options []*types.FieldOptions) ([]*types.TargetSelector, error) {

if len(fieldPaths) != len(options) {
// this should never happen
return nil, fmt.Errorf("internal error: length of fieldPaths != length of fieldOptions")
}

if patch, ok := patchTarget[file]; ok {
if !patch.Options["allowNameChange"] || !patch.Options["allowKindChange"] {
return writePatchTargets(patch, node, fieldPaths, options)
}
}

var result []*types.TargetSelector
meta, metaErr := node.GetMeta()

for i := range fieldPaths {
target := &types.TargetSelector{
Select: &types.Selector{
ResId: resid.ResId{
Name: node.GetName(),
Namespace: node.GetNamespace(),
Gvk: resid.Gvk{
Kind: node.GetKind(),
},
},
},
FieldPaths: []string{fieldPaths[i]},
}
if options[i].String() != "" {
target.Options = options[i]
}
if metaErr == nil {
if meta.TypeMeta.APIVersion != "" {
group, version := resid.ParseGroupVersion(meta.TypeMeta.APIVersion)
target.Select.ResId.Gvk.Group = group
target.Select.ResId.Gvk.Version = version
}
}

result = append(result, target)
}

return result, nil
}

// if the var appears in a patch, this must be handled differently than a regular
// resource because a patch may be applied to multiple resources and the resulting
// resources may have different IDs than the patch
func writePatchTargets(patch types.Patch, node *kyaml.RNode, fieldPaths []string,
options []*types.FieldOptions) ([]*types.TargetSelector, error) {

var result []*types.TargetSelector
selector := patch.Target.Copy()

for i := range fieldPaths {
target := &types.TargetSelector{
Select: &selector,
FieldPaths: []string{fieldPaths[i]},
}
if options[i].String() != "" {
target.Options = options[i]
}
if patch.Options["allowNameChange"] {
target.Select.ResId.Name = node.GetName()
}
if patch.Options["allowKindChange"] {
target.Select.ResId.Kind = node.GetKind()
}
if node.GetNamespace() != "" {
target.Select.ResId.Namespace = node.GetNamespace()
}
result = append(result, target)
}
return result, nil
}

func setPlaceholderValue(varName string, files []string, fSys filesys.FileSystem) error {
for _, filename := range files {
b, err := fSys.ReadFile(filename)
if err != nil {
continue
}
newFileContents := strings.ReplaceAll(string(b), fmt.Sprintf("$(%s)", varName),
fmt.Sprintf("%s_PLACEHOLDER", varName))
err = fSys.WriteFile(filename, []byte(newFileContents))
if err != nil {
return err
}
}
return nil
}

func stringInSlice(elem string, slice []string) bool {
for i := range slice {
if slice[i] == elem {
return true
}
}
return false
}

func indexOf(varName string, slice []string) int {
for i := range slice {
if slice[i] == varName {
return i
}
}
return -1
}

0 comments on commit f121e74

Please sign in to comment.