Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replacement poc #1631

Merged
merged 2 commits into from
Nov 21, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions api/types/replacement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package types

// Replacement defines how to perform a substitution
// where it is from and where it is to.
type Replacement struct {
Source *ReplSource `json:"source" yaml:"source"`
Target *ReplTarget `json:"target" yaml:"target"`
}

// ReplSource defines where a substitution is from
// It can from two different kinds of sources
// - from a field of one resource
// - from a string
type ReplSource struct {
ObjRef *Target `json:"objref,omitempty" yaml:"objref,omitempty"`
FieldRef string `json:"fieldref,omitempty" yaml:"fiedldref,omitempty"`
Value string `json:"value,omitempty" yaml:"value,omitempty"`
}

// ReplTarget defines where a substitution is to.
type ReplTarget struct {
ObjRef *Selector `json:"objref,omitempty" yaml:"objref,omitempty"`
FieldRefs []string `json:"fieldrefs,omitempty" yaml:"fieldrefs,omitempty"`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package main

import (
"fmt"
"regexp"
"strconv"
"strings"

"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/yaml"
)

var (
pattern = regexp.MustCompile(`(\S+)\[(\S+)=(\S+)\]`)
)

// Find matching image declarations and replace
// the name, tag and/or digest.
type plugin struct {
Replacements []types.Replacement `json:"replacements,omitempty" yaml:"replacements,omitempty"`
}

//noinspection GoUnusedGlobalVariable
var KustomizePlugin plugin

func (p *plugin) Config(
_ *resmap.PluginHelpers, c []byte) (err error) {
p.Replacements = []types.Replacement{}
err = yaml.Unmarshal(c, p)
if err != nil {
return err
}
for _, r := range p.Replacements {
if r.Source == nil {
return fmt.Errorf("`from` must be specified in one replacement")
}
if r.Target == nil {
return fmt.Errorf("`to` must be specified in one replacement")
}
count := 0
if r.Source.ObjRef != nil {
count += 1
}
if r.Source.Value != "" {
count += 1
}
if count > 1 {
return fmt.Errorf("only one of fieldref and value is allowed in one replacement")
}
}
return nil
}

func (p *plugin) Transform(m resmap.ResMap) (err error) {
for _, r := range p.Replacements {
var replacement interface{}
if r.Source.ObjRef != nil {
replacement, err = getReplacement(m, r.Source.ObjRef, r.Source.FieldRef)
if err != nil {
return err
}
}
if r.Source.Value != "" {
replacement = r.Source.Value
}
fmt.Printf("The replacement is %s\n", replacement)
err = substitute(m, r.Target, replacement)
if err != nil {
return err
}
}
return nil
}

func getReplacement(m resmap.ResMap, objRef *types.Target, fieldRef string) (interface{}, error) {
s := types.Selector{
Gvk: objRef.Gvk,
Name: objRef.Name,
Namespace: objRef.Namespace,
}
resources, err := m.Select(s)
if err != nil {
return "", err
}
if len(resources) > 1 {
return "", fmt.Errorf("found more than one resources matching from %v", resources)
}
if len(resources) == 0 {
return "", fmt.Errorf("failed to find one resource matching from %v", objRef)
}
if fieldRef == "" {
fieldRef = ".metadata.name"
}
return resources[0].GetFieldValue(fieldRef)
}

func substitute(m resmap.ResMap, to *types.ReplTarget, replacement interface{}) error {
resources, err := m.Select(*to.ObjRef)
if err != nil {
return err
}
for _, r := range resources {
for _, p := range to.FieldRefs {
pathSlice := strings.Split(p, ".")
if err := updateField(r.Map(), pathSlice, replacement); err != nil {
return err
}
}
}
return nil
}

func getFirstPathSegment(path string) (field string, key string, value string, array bool) {
groups := pattern.FindStringSubmatch(path)
if len(groups) != 4 {
return path, "", "", false
}
return groups[1], groups[2], groups[3], groups[2] != ""
}

func updateField(m interface{}, pathToField []string, replacement interface{}) error {
if len(pathToField) == 0 {
return nil
}

switch typedM := m.(type) {
case map[string]interface{}:
return updateMapField(typedM, pathToField, replacement)
case []interface{}:
return updateSliceField(typedM, pathToField, replacement)
default:
return fmt.Errorf("%#v is not expected to be a primitive type", typedM)
}
}

func updateMapField(m map[string]interface{}, pathToField []string, replacement interface{}) error {
path, key, value, isArray := getFirstPathSegment(pathToField[0])

v, found := m[path]
if !found {
m[path] = map[string]interface{}{}
v = m[path]
}

if len(pathToField) == 1 {
if !isArray {
m[path] = replacement
return nil
}
switch typedV := v.(type) {
case nil:
fmt.Printf("nil vlaue at `%s` ignored in mutation attempt", strings.Join(pathToField, "."))
case []interface{}:
for i := range typedV {
item := typedV[i]
typedItem, ok := item.(map[string]interface{})
if !ok {
return fmt.Errorf("%#v is expected to be %T", item, typedItem)
}
if actualValue, ok := typedItem[key]; ok {
if value == actualValue {
typedItem[key] = value
}
}
}
default:
return fmt.Errorf("%#v is not expected to be a primitive type", typedV)
}
}

newPathToField := pathToField[1:]
switch typedV := v.(type) {
case nil:
fmt.Printf(
"nil value at `%s` ignored in mutation attempt",
strings.Join(pathToField, "."))
return nil
case map[string]interface{}:
return updateField(typedV, newPathToField, replacement)
case []interface{}:
if !isArray {
return updateField(typedV, newPathToField, replacement)
}
for i := range typedV {
item := typedV[i]
typedItem, ok := item.(map[string]interface{})
if !ok {
return fmt.Errorf("%#v is expected to be %T", item, typedItem)
}
if actualValue, ok := typedItem[key]; ok {
if value == actualValue {
return updateField(typedItem, newPathToField, replacement)
}
}
}
default:
return fmt.Errorf("%#v is not expected to be a primitive type", typedV)
}
return nil
}

func updateSliceField(m []interface{}, pathToField []string, replacement interface{}) error {
if len(pathToField) == 0 {
return nil
}
index, err := strconv.Atoi(pathToField[0])
if err != nil {
return err
}
if len(m) > index && index >= 0 {
if len(pathToField) == 1 {
m[index] = replacement
return nil
} else {
return updateField(m[index], pathToField[1:], replacement)
}
}
return fmt.Errorf("index %v is out ouf bound", index)
}
Loading