-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1631 from Liujingfang1/replacement-poc
Replacement poc
- Loading branch information
Showing
5 changed files
with
1,060 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"` | ||
} |
223 changes: 223 additions & 0 deletions
223
plugin/someteam.example.com/v1/replacementtransformer/ReplacementTransformer.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.