mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-29 17:41:13 +00:00
Compare commits
106 Commits
api/v0.8.5
...
kustomize/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7487e2f9cb | ||
|
|
59af49522e | ||
|
|
72d3eb15e0 | ||
|
|
14e31de6b1 | ||
|
|
162b8f3d37 | ||
|
|
003cf61a48 | ||
|
|
74f0df8b9d | ||
|
|
0df531e7c6 | ||
|
|
e3ce61647f | ||
|
|
768132f65f | ||
|
|
6a708bcc23 | ||
|
|
d8182f8d81 | ||
|
|
99b6a5920e | ||
|
|
8877c81468 | ||
|
|
88911bbb61 | ||
|
|
01c477570a | ||
|
|
f8dad80a79 | ||
|
|
240cda089a | ||
|
|
c94c193b5b | ||
|
|
9989b5fc84 | ||
|
|
aeba50488b | ||
|
|
9c43518a15 | ||
|
|
9d50890174 | ||
|
|
3af1ae4159 | ||
|
|
5100568b0c | ||
|
|
aa5b4814d6 | ||
|
|
264b3ff338 | ||
|
|
a40c74e545 | ||
|
|
f61b075d3b | ||
|
|
629d822604 | ||
|
|
bf64f109b9 | ||
|
|
5f93fc53f4 | ||
|
|
0fe3f303e8 | ||
|
|
7825050b18 | ||
|
|
ed688a87e4 | ||
|
|
629fdee26a | ||
|
|
ca58ce775a | ||
|
|
0990d96c52 | ||
|
|
94c45e0f9f | ||
|
|
ca527a8e4c | ||
|
|
fa0b237178 | ||
|
|
a9bcf7187a | ||
|
|
a49d429909 | ||
|
|
c63288024d | ||
|
|
f374a12f24 | ||
|
|
c7156d0586 | ||
|
|
d3b7d3ab70 | ||
|
|
197bb9d9e3 | ||
|
|
fa96878cfc | ||
|
|
558995536d | ||
|
|
3255c73c71 | ||
|
|
fd486c1f23 | ||
|
|
b0a40e2752 | ||
|
|
ccb95ab269 | ||
|
|
e6b52e7295 | ||
|
|
4a6ec9063d | ||
|
|
c3beadacd9 | ||
|
|
5f3bd4b4c2 | ||
|
|
e77c284924 | ||
|
|
5ed2067be9 | ||
|
|
7b38ce4ef2 | ||
|
|
700a112b28 | ||
|
|
e05ce0f05b | ||
|
|
b8cfa3ca9b | ||
|
|
26a8455717 | ||
|
|
710db98dbf | ||
|
|
0d152c4784 | ||
|
|
1729c95135 | ||
|
|
6f6d41f17f | ||
|
|
3ff5263ff6 | ||
|
|
5bb668533f | ||
|
|
eb48b1b718 | ||
|
|
1f837fdfec | ||
|
|
1301384670 | ||
|
|
a9c20a2eb7 | ||
|
|
297bdc3825 | ||
|
|
831f99c95b | ||
|
|
5247aa5750 | ||
|
|
74d5646526 | ||
|
|
2f6a611e62 | ||
|
|
d0dbc3e87b | ||
|
|
235101a614 | ||
|
|
123a5d6e56 | ||
|
|
e4bbd04a43 | ||
|
|
75120b2a92 | ||
|
|
cb423ad300 | ||
|
|
c81b5bd3c2 | ||
|
|
b3cec39c25 | ||
|
|
5fc6cab49f | ||
|
|
c636ee616b | ||
|
|
f96ac2d61e | ||
|
|
a513c56d88 | ||
|
|
ed3ab9f532 | ||
|
|
bab8c34c1f | ||
|
|
839fd2b971 | ||
|
|
26e9b8b3b8 | ||
|
|
ddfb4ff02d | ||
|
|
a5e6295923 | ||
|
|
e2e495027d | ||
|
|
397744f436 | ||
|
|
9e8e7a7fe9 | ||
|
|
4d66f9a093 | ||
|
|
d203c2328a | ||
|
|
ec0e42709a | ||
|
|
abae65d8f1 | ||
|
|
054f18701a |
27
Makefile
27
Makefile
@@ -3,8 +3,11 @@
|
|||||||
#
|
#
|
||||||
# Makefile for kustomize CLI and API.
|
# Makefile for kustomize CLI and API.
|
||||||
|
|
||||||
MYGOBIN := $(shell go env GOPATH)/bin
|
|
||||||
SHELL := /usr/bin/env bash
|
SHELL := /usr/bin/env bash
|
||||||
|
MYGOBIN = $(shell go env GOBIN)
|
||||||
|
ifeq ($(MYGOBIN),)
|
||||||
|
MYGOBIN = $(shell go env GOPATH)/bin
|
||||||
|
endif
|
||||||
export PATH := $(MYGOBIN):$(PATH)
|
export PATH := $(MYGOBIN):$(PATH)
|
||||||
MODULES := '"cmd/config" "api/" "kustomize/" "kyaml/"'
|
MODULES := '"cmd/config" "api/" "kustomize/" "kyaml/"'
|
||||||
|
|
||||||
@@ -26,8 +29,7 @@ verify-kustomize: \
|
|||||||
lint-kustomize \
|
lint-kustomize \
|
||||||
test-unit-kustomize-all \
|
test-unit-kustomize-all \
|
||||||
test-examples-kustomize-against-HEAD \
|
test-examples-kustomize-against-HEAD \
|
||||||
test-examples-kustomize-against-4.0 \
|
test-examples-kustomize-against-4.0
|
||||||
test-examples-kustomize-against-3.10
|
|
||||||
|
|
||||||
# The following target referenced by a file in
|
# The following target referenced by a file in
|
||||||
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
|
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
|
||||||
@@ -39,8 +41,7 @@ prow-presubmit-check: \
|
|||||||
test-unit-cmd-all \
|
test-unit-cmd-all \
|
||||||
test-go-mod \
|
test-go-mod \
|
||||||
test-examples-kustomize-against-HEAD \
|
test-examples-kustomize-against-HEAD \
|
||||||
test-examples-kustomize-against-4.0 \
|
test-examples-kustomize-against-4.0
|
||||||
test-examples-kustomize-against-3.10
|
|
||||||
|
|
||||||
.PHONY: verify-kustomize-e2e
|
.PHONY: verify-kustomize-e2e
|
||||||
verify-kustomize-e2e: test-examples-e2e-kustomize
|
verify-kustomize-e2e: test-examples-e2e-kustomize
|
||||||
@@ -95,7 +96,7 @@ $(MYGOBIN)/prchecker:
|
|||||||
go install .
|
go install .
|
||||||
|
|
||||||
# Build from local source.
|
# Build from local source.
|
||||||
$(MYGOBIN)/kustomize:
|
$(MYGOBIN)/kustomize: build-kustomize-api
|
||||||
cd kustomize; \
|
cd kustomize; \
|
||||||
go install .
|
go install .
|
||||||
|
|
||||||
@@ -104,7 +105,7 @@ install-tools: \
|
|||||||
$(MYGOBIN)/goimports \
|
$(MYGOBIN)/goimports \
|
||||||
$(MYGOBIN)/golangci-lint-kustomize \
|
$(MYGOBIN)/golangci-lint-kustomize \
|
||||||
$(MYGOBIN)/gorepomod \
|
$(MYGOBIN)/gorepomod \
|
||||||
$(MYGOBIN)/helm \
|
$(MYGOBIN)/helmV3 \
|
||||||
$(MYGOBIN)/k8scopy \
|
$(MYGOBIN)/k8scopy \
|
||||||
$(MYGOBIN)/mdrip \
|
$(MYGOBIN)/mdrip \
|
||||||
$(MYGOBIN)/pluginator \
|
$(MYGOBIN)/pluginator \
|
||||||
@@ -282,11 +283,7 @@ test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
|
|||||||
|
|
||||||
.PHONY:
|
.PHONY:
|
||||||
test-examples-kustomize-against-4.0: $(MYGOBIN)/mdrip
|
test-examples-kustomize-against-4.0: $(MYGOBIN)/mdrip
|
||||||
./hack/testExamplesAgainstKustomize.sh v4@v4.0.1
|
./hack/testExamplesAgainstKustomize.sh v4@v4.0.5
|
||||||
|
|
||||||
.PHONY:
|
|
||||||
test-examples-kustomize-against-3.10: $(MYGOBIN)/mdrip
|
|
||||||
./hack/testExamplesAgainstKustomize.sh v3@v3.10.0
|
|
||||||
|
|
||||||
# linux only.
|
# linux only.
|
||||||
# This is for testing an example plugin that
|
# This is for testing an example plugin that
|
||||||
@@ -326,17 +323,13 @@ $(MYGOBIN)/helmV3:
|
|||||||
( \
|
( \
|
||||||
set -e; \
|
set -e; \
|
||||||
d=$(shell mktemp -d); cd $$d; \
|
d=$(shell mktemp -d); cd $$d; \
|
||||||
tgzFile=helm-v3.4.0-linux-amd64.tar.gz; \
|
tgzFile=helm-v3.5.3-linux-amd64.tar.gz; \
|
||||||
wget https://get.helm.sh/$$tgzFile; \
|
wget https://get.helm.sh/$$tgzFile; \
|
||||||
tar -xvzf $$tgzFile; \
|
tar -xvzf $$tgzFile; \
|
||||||
mv linux-amd64/helm $(MYGOBIN)/helmV3; \
|
mv linux-amd64/helm $(MYGOBIN)/helmV3; \
|
||||||
rm -rf $$d \
|
rm -rf $$d \
|
||||||
)
|
)
|
||||||
|
|
||||||
# Default version of helm is v3.
|
|
||||||
$(MYGOBIN)/helm: $(MYGOBIN)/helmV3
|
|
||||||
ln -s $(MYGOBIN)/helmV3 $(MYGOBIN)/helm
|
|
||||||
|
|
||||||
$(MYGOBIN)/kind:
|
$(MYGOBIN)/kind:
|
||||||
( \
|
( \
|
||||||
set -e; \
|
set -e; \
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ aliases:
|
|||||||
- mortent
|
- mortent
|
||||||
- phanimarupaka
|
- phanimarupaka
|
||||||
- Shell32-Natsu
|
- Shell32-Natsu
|
||||||
|
- natasha41575
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type HashTransformerPlugin struct {
|
type HashTransformerPlugin struct {
|
||||||
hasher ifc.KunstructuredHasher
|
hasher ifc.KustHasher
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HashTransformerPlugin) Config(
|
func (p *HashTransformerPlugin) Config(
|
||||||
@@ -24,7 +24,7 @@ func (p *HashTransformerPlugin) Config(
|
|||||||
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
|
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||||
for _, res := range m.Resources() {
|
for _, res := range m.Resources() {
|
||||||
if res.NeedHashSuffix() {
|
if res.NeedHashSuffix() {
|
||||||
h, err := p.hasher.Hash(res)
|
h, err := res.Hash(p.hasher)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package builtins
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -16,7 +15,6 @@ import (
|
|||||||
|
|
||||||
"github.com/imdario/mergo"
|
"github.com/imdario/mergo"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
@@ -25,243 +23,285 @@ import (
|
|||||||
// HelmChartInflationGeneratorPlugin is a plugin to generate resources
|
// HelmChartInflationGeneratorPlugin is a plugin to generate resources
|
||||||
// from a remote or local helm chart.
|
// from a remote or local helm chart.
|
||||||
type HelmChartInflationGeneratorPlugin struct {
|
type HelmChartInflationGeneratorPlugin struct {
|
||||||
h *resmap.PluginHelpers
|
h *resmap.PluginHelpers
|
||||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
types.HelmGlobals
|
||||||
runHelmCommand func([]string) ([]byte, error)
|
types.HelmChart
|
||||||
types.HelmChartArgs
|
|
||||||
tmpDir string
|
tmpDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
var KustomizePlugin HelmChartInflationGeneratorPlugin
|
var KustomizePlugin HelmChartInflationGeneratorPlugin
|
||||||
|
|
||||||
|
const (
|
||||||
|
valuesMergeOptionMerge = "merge"
|
||||||
|
valuesMergeOptionOverride = "override"
|
||||||
|
valuesMergeOptionReplace = "replace"
|
||||||
|
)
|
||||||
|
|
||||||
|
var legalMergeOptions = []string{
|
||||||
|
valuesMergeOptionMerge,
|
||||||
|
valuesMergeOptionOverride,
|
||||||
|
valuesMergeOptionReplace,
|
||||||
|
}
|
||||||
|
|
||||||
// Config uses the input plugin configurations `config` to setup the generator
|
// Config uses the input plugin configurations `config` to setup the generator
|
||||||
// options
|
// options
|
||||||
func (p *HelmChartInflationGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) error {
|
func (p *HelmChartInflationGeneratorPlugin) Config(
|
||||||
|
h *resmap.PluginHelpers, config []byte) (err error) {
|
||||||
|
if h.GeneralConfig() == nil {
|
||||||
|
return fmt.Errorf("unable to access general config")
|
||||||
|
}
|
||||||
|
if !h.GeneralConfig().HelmConfig.Enabled {
|
||||||
|
return fmt.Errorf("must specify --enable-helm")
|
||||||
|
}
|
||||||
|
if h.GeneralConfig().HelmConfig.Command == "" {
|
||||||
|
return fmt.Errorf("must specify --helm-command")
|
||||||
|
}
|
||||||
p.h = h
|
p.h = h
|
||||||
err := yaml.Unmarshal(config, p)
|
if err = yaml.Unmarshal(config, p); err != nil {
|
||||||
if err != nil {
|
return
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
tmpDir, err := filesys.NewTmpConfirmedDir()
|
return p.validateArgs()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.tmpDir = string(tmpDir)
|
|
||||||
if p.ChartName == "" {
|
|
||||||
return fmt.Errorf("chartName cannot be empty")
|
|
||||||
}
|
|
||||||
if p.ChartHome == "" {
|
|
||||||
p.ChartHome = filepath.Join(p.tmpDir, "chart")
|
|
||||||
}
|
|
||||||
if p.ChartRepoName == "" {
|
|
||||||
p.ChartRepoName = "stable"
|
|
||||||
}
|
|
||||||
if p.HelmBin == "" {
|
|
||||||
p.HelmBin = "helm"
|
|
||||||
}
|
|
||||||
if p.HelmHome == "" {
|
|
||||||
p.HelmHome = filepath.Join(p.tmpDir, ".helm")
|
|
||||||
}
|
|
||||||
if p.Values == "" {
|
|
||||||
p.Values = filepath.Join(p.ChartHome, p.ChartName, "values.yaml")
|
|
||||||
}
|
|
||||||
if p.ValuesMerge == "" {
|
|
||||||
p.ValuesMerge = "override"
|
|
||||||
}
|
|
||||||
// runHelmCommand will run `helm` command with args provided. Return stdout
|
|
||||||
// and error if there is any.
|
|
||||||
p.runHelmCommand = func(args []string) ([]byte, error) {
|
|
||||||
stdout := new(bytes.Buffer)
|
|
||||||
stderr := new(bytes.Buffer)
|
|
||||||
cmd := exec.Command(p.HelmBin, args...)
|
|
||||||
cmd.Stdout = stdout
|
|
||||||
cmd.Stderr = stderr
|
|
||||||
cmd.Env = append(cmd.Env,
|
|
||||||
fmt.Sprintf("HELM_CONFIG_HOME=%s", p.HelmHome),
|
|
||||||
fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.HelmHome),
|
|
||||||
fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.HelmHome),
|
|
||||||
)
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return stdout.Bytes(),
|
|
||||||
errors.Wrap(
|
|
||||||
fmt.Errorf("failed to run command %s %s", p.HelmBin, strings.Join(args, " ")),
|
|
||||||
stderr.String(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return stdout.Bytes(), nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EncodeValues for writing
|
// This uses the real file system since tmpDir may be used
|
||||||
func (p *HelmChartInflationGeneratorPlugin) EncodeValues(w io.Writer) error {
|
// by the helm subprocess. Cannot use a chroot jail or fake
|
||||||
d, err := yaml.Marshal(p.ValuesLocal)
|
// filesystem since we allow the user to use previously
|
||||||
if err != nil {
|
// downloaded charts. This is safe since this plugin is
|
||||||
return err
|
// owned by kustomize.
|
||||||
}
|
func (p *HelmChartInflationGeneratorPlugin) establishTmpDir() (err error) {
|
||||||
_, err = w.Write(d)
|
if p.tmpDir != "" {
|
||||||
if err != nil {
|
// already done.
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// useValuesLocal process (merge) inflator config provided values with chart default values.yaml
|
|
||||||
func (p *HelmChartInflationGeneratorPlugin) useValuesLocal() error {
|
|
||||||
// not override, merge, none
|
|
||||||
if !(p.ValuesMerge == "none" || p.ValuesMerge == "no" || p.ValuesMerge == "false") {
|
|
||||||
var pValues []byte
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if filepath.IsAbs(p.Values) {
|
|
||||||
pValues, err = ioutil.ReadFile(p.Values)
|
|
||||||
} else {
|
|
||||||
pValues, err = p.h.Loader().Load(p.Values)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
chValues := make(map[string]interface{})
|
|
||||||
err = yaml.Unmarshal(pValues, &chValues)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if p.ValuesMerge == "override" {
|
|
||||||
err = mergo.Merge(&chValues, p.ValuesLocal, mergo.WithOverride)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.ValuesMerge == "merge" {
|
|
||||||
err = mergo.Merge(&chValues, p.ValuesLocal)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.ValuesLocal = chValues
|
|
||||||
}
|
|
||||||
b, err := yaml.Marshal(p.ValuesLocal)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
path, err := p.writeValuesBytes(b)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.Values = path
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// copyValues will copy the relative values file into the temp directory
|
|
||||||
// to avoid messing up with CWD.
|
|
||||||
func (p *HelmChartInflationGeneratorPlugin) copyValues() error {
|
|
||||||
// only copy when the values path is not absolute
|
|
||||||
if filepath.IsAbs(p.Values) {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// we must use use loader to read values file
|
p.tmpDir, err = ioutil.TempDir("", "kustomize-helm-")
|
||||||
b, err := p.h.Loader().Load(p.Values)
|
return err
|
||||||
if err != nil {
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) validateArgs() (err error) {
|
||||||
|
if p.Name == "" {
|
||||||
|
return fmt.Errorf("chart name cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChartHome might be consulted by the plugin (to read
|
||||||
|
// values files below it), so it must be located under
|
||||||
|
// the loader root (unless root restrictions are
|
||||||
|
// disabled, in which case this can be an absolute path).
|
||||||
|
if p.ChartHome == "" {
|
||||||
|
p.ChartHome = "charts"
|
||||||
|
}
|
||||||
|
|
||||||
|
// The ValuesFile may be consulted by the plugin, so it must
|
||||||
|
// be under the loader root (unless root restrictions are
|
||||||
|
// disabled).
|
||||||
|
if p.ValuesFile == "" {
|
||||||
|
p.ValuesFile = filepath.Join(p.ChartHome, p.Name, "values.yaml")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = p.errIfIllegalValuesMerge(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
path, err := p.writeValuesBytes(b)
|
|
||||||
if err != nil {
|
// ConfigHome is not loaded by the plugin, and can be located anywhere.
|
||||||
return err
|
if p.ConfigHome == "" {
|
||||||
|
if err = p.establishTmpDir(); err != nil {
|
||||||
|
return errors.Wrap(
|
||||||
|
err, "unable to create tmp dir for HELM_CONFIG_HOME")
|
||||||
|
}
|
||||||
|
p.ConfigHome = filepath.Join(p.tmpDir, "helm")
|
||||||
}
|
}
|
||||||
p.Values = path
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HelmChartInflationGeneratorPlugin) writeValuesBytes(b []byte) (string, error) {
|
func (p *HelmChartInflationGeneratorPlugin) errIfIllegalValuesMerge() error {
|
||||||
path := filepath.Join(p.ChartHome, p.ChartName, "kustomize-values.yaml")
|
if p.ValuesMerge == "" {
|
||||||
err := ioutil.WriteFile(path, b, 0644)
|
// Use the default.
|
||||||
|
p.ValuesMerge = valuesMergeOptionOverride
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, opt := range legalMergeOptions {
|
||||||
|
if p.ValuesMerge == opt {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("valuesMerge must be one of %v", legalMergeOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) absChartHome() string {
|
||||||
|
if filepath.IsAbs(p.ChartHome) {
|
||||||
|
return p.ChartHome
|
||||||
|
}
|
||||||
|
return filepath.Join(p.h.Loader().Root(), p.ChartHome)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) runHelmCommand(
|
||||||
|
args []string) ([]byte, error) {
|
||||||
|
stdout := new(bytes.Buffer)
|
||||||
|
stderr := new(bytes.Buffer)
|
||||||
|
cmd := exec.Command(p.h.GeneralConfig().HelmConfig.Command, args...)
|
||||||
|
cmd.Stdout = stdout
|
||||||
|
cmd.Stderr = stderr
|
||||||
|
env := []string{
|
||||||
|
fmt.Sprintf("HELM_CONFIG_HOME=%s", p.ConfigHome),
|
||||||
|
fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.ConfigHome),
|
||||||
|
fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.ConfigHome)}
|
||||||
|
cmd.Env = append(os.Environ(), env...)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
helm := p.h.GeneralConfig().HelmConfig.Command
|
||||||
|
err = errors.Wrap(
|
||||||
|
fmt.Errorf(
|
||||||
|
"unable to run: '%s %s' with env=%s (is '%s' installed?)",
|
||||||
|
helm, strings.Join(args, " "), env, helm),
|
||||||
|
stderr.String(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return stdout.Bytes(), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// createNewMergedValuesFile replaces/merges original values file with ValuesInline.
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) createNewMergedValuesFile() (
|
||||||
|
path string, err error) {
|
||||||
|
if p.ValuesMerge == valuesMergeOptionMerge ||
|
||||||
|
p.ValuesMerge == valuesMergeOptionOverride {
|
||||||
|
if err = p.replaceValuesInline(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var b []byte
|
||||||
|
b, err = yaml.Marshal(p.ValuesInline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return path, nil
|
return p.writeValuesBytes(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) replaceValuesInline() error {
|
||||||
|
pValues, err := p.h.Loader().Load(p.ValuesFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
chValues := make(map[string]interface{})
|
||||||
|
if err = yaml.Unmarshal(pValues, &chValues); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch p.ValuesMerge {
|
||||||
|
case valuesMergeOptionOverride:
|
||||||
|
err = mergo.Merge(
|
||||||
|
&chValues, p.ValuesInline, mergo.WithOverride)
|
||||||
|
case valuesMergeOptionMerge:
|
||||||
|
err = mergo.Merge(&chValues, p.ValuesInline)
|
||||||
|
}
|
||||||
|
p.ValuesInline = chValues
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyValuesFile to avoid branching. TODO: get rid of this.
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) copyValuesFile() (string, error) {
|
||||||
|
b, err := p.h.Loader().Load(p.ValuesFile)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return p.writeValuesBytes(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a absolute path file in the tmp file system.
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) writeValuesBytes(
|
||||||
|
b []byte) (string, error) {
|
||||||
|
if err := p.establishTmpDir(); err != nil {
|
||||||
|
return "", fmt.Errorf("cannot create tmp dir to write helm values")
|
||||||
|
}
|
||||||
|
path := filepath.Join(p.tmpDir, p.Name+"-kustomize-values.yaml")
|
||||||
|
return path, ioutil.WriteFile(path, b, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *HelmChartInflationGeneratorPlugin) cleanup() {
|
||||||
|
if p.tmpDir != "" {
|
||||||
|
os.RemoveAll(p.tmpDir)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate implements generator
|
// Generate implements generator
|
||||||
func (p *HelmChartInflationGeneratorPlugin) Generate() (resmap.ResMap, error) {
|
func (p *HelmChartInflationGeneratorPlugin) Generate() (rm resmap.ResMap, err error) {
|
||||||
// cleanup
|
defer p.cleanup()
|
||||||
defer os.RemoveAll(p.tmpDir)
|
if err = p.checkHelmVersion(); err != nil {
|
||||||
// check helm version. we only support V3
|
|
||||||
err := p.checkHelmVersion()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// pull the chart
|
if path, exists := p.chartExistsLocally(); !exists {
|
||||||
if !p.checkLocalChart() {
|
if p.Repo == "" {
|
||||||
_, err := p.runHelmCommand(p.getPullCommandArgs())
|
return nil, fmt.Errorf(
|
||||||
if err != nil {
|
"no repo specified for pull, no chart found at '%s'", path)
|
||||||
|
}
|
||||||
|
if _, err := p.runHelmCommand(p.pullCommand()); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if len(p.ValuesInline) > 0 {
|
||||||
// inflator config valuesLocal
|
p.ValuesFile, err = p.createNewMergedValuesFile()
|
||||||
if len(p.ValuesLocal) > 0 {
|
|
||||||
err := p.useValuesLocal()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
err := p.copyValues()
|
p.ValuesFile, err = p.copyValuesFile()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
// render the charts
|
return nil, err
|
||||||
stdout, err := p.runHelmCommand(p.getTemplateCommandArgs())
|
}
|
||||||
|
var stdout []byte
|
||||||
|
stdout, err = p.runHelmCommand(p.templateCommand())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return p.h.ResmapFactory().NewResMapFromBytes(stdout)
|
rm, err = p.h.ResmapFactory().NewResMapFromBytes(stdout)
|
||||||
|
if err == nil {
|
||||||
|
return rm, nil
|
||||||
|
}
|
||||||
|
// try to remove the contents before first "---" because
|
||||||
|
// helm may produce messages to stdout before it
|
||||||
|
stdoutStr := string(stdout)
|
||||||
|
if idx := strings.Index(stdoutStr, "---"); idx != -1 {
|
||||||
|
return p.h.ResmapFactory().NewResMapFromBytes([]byte(stdoutStr[idx:]))
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HelmChartInflationGeneratorPlugin) getTemplateCommandArgs() []string {
|
func (p *HelmChartInflationGeneratorPlugin) templateCommand() []string {
|
||||||
args := []string{"template"}
|
args := []string{"template"}
|
||||||
if p.ReleaseName != "" {
|
if p.ReleaseName != "" {
|
||||||
args = append(args, p.ReleaseName)
|
args = append(args, p.ReleaseName)
|
||||||
}
|
}
|
||||||
args = append(args, filepath.Join(p.ChartHome, p.ChartName))
|
args = append(args, filepath.Join(p.absChartHome(), p.Name))
|
||||||
if p.ReleaseNamespace != "" {
|
if p.ValuesFile != "" {
|
||||||
args = append(args, "--namespace", p.ReleaseNamespace)
|
args = append(args, "--values", p.ValuesFile)
|
||||||
}
|
}
|
||||||
if p.Values != "" {
|
if p.ReleaseName == "" {
|
||||||
args = append(args, "--values", p.Values)
|
// AFAICT, this doesn't work as intended due to a bug in helm.
|
||||||
|
// See https://github.com/helm/helm/issues/6019
|
||||||
|
// I've tried placing the flag before and after the name argument.
|
||||||
|
args = append(args, "--generate-name")
|
||||||
}
|
}
|
||||||
args = append(args, p.ExtraArgs...)
|
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *HelmChartInflationGeneratorPlugin) getPullCommandArgs() []string {
|
func (p *HelmChartInflationGeneratorPlugin) pullCommand() []string {
|
||||||
args := []string{"pull", "--untar", "--untardir", p.ChartHome}
|
args := []string{
|
||||||
chartName := fmt.Sprintf("%s/%s", p.ChartRepoName, p.ChartName)
|
"pull",
|
||||||
if p.ChartVersion != "" {
|
"--untar",
|
||||||
args = append(args, "--version", p.ChartVersion)
|
"--untardir", p.absChartHome(),
|
||||||
|
"--repo", p.Repo,
|
||||||
|
p.Name}
|
||||||
|
if p.Version != "" {
|
||||||
|
args = append(args, "--version", p.Version)
|
||||||
}
|
}
|
||||||
if p.ChartRepoURL != "" {
|
|
||||||
args = append(args, "--repo", p.ChartRepoURL)
|
|
||||||
chartName = p.ChartName
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, chartName)
|
|
||||||
|
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkLocalChart will return true if the chart does exist in
|
// chartExistsLocally will return true if the chart does exist in
|
||||||
// local chart home.
|
// local chart home.
|
||||||
func (p *HelmChartInflationGeneratorPlugin) checkLocalChart() bool {
|
func (p *HelmChartInflationGeneratorPlugin) chartExistsLocally() (string, bool) {
|
||||||
path := filepath.Join(p.ChartHome, p.ChartName)
|
path := filepath.Join(p.absChartHome(), p.Name)
|
||||||
s, err := os.Stat(path)
|
s, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return "", false
|
||||||
}
|
}
|
||||||
return s.IsDir()
|
return path, s.IsDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkHelmVersion will return an error if the helm version is not V3
|
// checkHelmVersion will return an error if the helm version is not V3
|
||||||
@@ -270,11 +310,17 @@ func (p *HelmChartInflationGeneratorPlugin) checkHelmVersion() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r, err := regexp.Compile(`v\d+(\.\d+)+`)
|
r, err := regexp.Compile(`v?\d+(\.\d+)+`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v := string(r.Find(stdout))[1:]
|
v := r.FindString(string(stdout))
|
||||||
|
if v == "" {
|
||||||
|
return fmt.Errorf("cannot find version string in %s", string(stdout))
|
||||||
|
}
|
||||||
|
if v[0] == 'v' {
|
||||||
|
v = v[1:]
|
||||||
|
}
|
||||||
majorVersion := strings.Split(v, ".")[0]
|
majorVersion := strings.Split(v, ".")[0]
|
||||||
if majorVersion != "3" {
|
if majorVersion != "3" {
|
||||||
return fmt.Errorf("this plugin requires helm V3 but got v%s", v)
|
return fmt.Errorf("this plugin requires helm V3 but got v%s", v)
|
||||||
|
|||||||
@@ -30,20 +30,15 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.Resources() {
|
||||||
empty, err := r.IsEmpty()
|
if r.IsEmpty() {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if empty {
|
|
||||||
// Don't mutate empty objects?
|
// Don't mutate empty objects?
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r.StorePreviousId()
|
r.StorePreviousId()
|
||||||
err = r.ApplyFilter(namespace.Filter{
|
if err := r.ApplyFilter(namespace.Filter{
|
||||||
Namespace: p.Namespace,
|
Namespace: p.Namespace,
|
||||||
FsSlice: p.FieldSpecs,
|
FsSlice: p.FieldSpecs,
|
||||||
})
|
}); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
||||||
|
|||||||
@@ -28,45 +28,48 @@ func (p *PatchStrategicMergeTransformerPlugin) Config(
|
|||||||
return fmt.Errorf("empty file path and empty patch content")
|
return fmt.Errorf("empty file path and empty patch content")
|
||||||
}
|
}
|
||||||
if len(p.Paths) != 0 {
|
if len(p.Paths) != 0 {
|
||||||
for _, onePath := range p.Paths {
|
patches, err := loadFromPaths(h, p.Paths)
|
||||||
// The following oddly attempts to interpret a path string as an
|
|
||||||
// actual patch (instead of as a path to a file containing a patch).
|
|
||||||
// All tests pass if this code is commented out. This code should
|
|
||||||
// be deleted; the user should use the Patches field which
|
|
||||||
// exists for this purpose (inline patch declaration).
|
|
||||||
res, err := h.ResmapFactory().RF().SliceFromBytes([]byte(onePath))
|
|
||||||
if err == nil {
|
|
||||||
p.loadedPatches = append(p.loadedPatches, res...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
res, err = h.ResmapFactory().RF().SliceFromPatches(
|
|
||||||
h.Loader(), []types.PatchStrategicMerge{onePath})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.loadedPatches = append(p.loadedPatches, res...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.Patches != "" {
|
|
||||||
res, err := h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
p.loadedPatches = append(p.loadedPatches, res...)
|
p.loadedPatches = append(p.loadedPatches, patches...)
|
||||||
|
}
|
||||||
|
if p.Patches != "" {
|
||||||
|
patches, err := h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.loadedPatches = append(p.loadedPatches, patches...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(p.loadedPatches) == 0 {
|
if len(p.loadedPatches) == 0 {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches)
|
"patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches)
|
||||||
}
|
}
|
||||||
// Merge the patches, looking for conflicts.
|
|
||||||
_, err = h.ResmapFactory().ConflatePatches(p.loadedPatches)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadFromPaths(
|
||||||
|
h *resmap.PluginHelpers,
|
||||||
|
paths []types.PatchStrategicMerge) (
|
||||||
|
result []*resource.Resource, err error) {
|
||||||
|
var patches []*resource.Resource
|
||||||
|
for _, path := range paths {
|
||||||
|
// For legacy reasons, attempt to treat the path string as
|
||||||
|
// actual patch content.
|
||||||
|
patches, err = h.ResmapFactory().RF().SliceFromBytes([]byte(path))
|
||||||
|
if err != nil {
|
||||||
|
// Failing that, treat it as a file path.
|
||||||
|
patches, err = h.ResmapFactory().RF().SliceFromPatches(
|
||||||
|
h.Loader(), []types.PatchStrategicMerge{path})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = append(result, patches...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error {
|
func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||||
for _, patch := range p.loadedPatches {
|
for _, patch := range p.loadedPatches {
|
||||||
target, err := m.GetById(patch.OrgId())
|
target, err := m.GetById(patch.OrgId())
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ type PatchTransformerPlugin struct {
|
|||||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||||
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||||
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
||||||
|
Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PatchTransformerPlugin) Config(
|
func (p *PatchTransformerPlugin) Config(
|
||||||
@@ -60,6 +61,12 @@ func (p *PatchTransformerPlugin) Config(
|
|||||||
}
|
}
|
||||||
if errSM == nil {
|
if errSM == nil {
|
||||||
p.loadedPatch = patchSM
|
p.loadedPatch = patchSM
|
||||||
|
if p.Options["allowNameChange"] {
|
||||||
|
p.loadedPatch.SetAllowNameChange("true")
|
||||||
|
}
|
||||||
|
if p.Options["allowKindChange"] {
|
||||||
|
p.loadedPatch.SetAllowKindChange("true")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
p.decodedPatch = patchJson
|
p.decodedPatch = patchJson
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package nameref
|
package nameref
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -11,7 +10,6 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
@@ -186,11 +184,7 @@ func (f Filter) recordTheReferral(referral *resource.Resource) {
|
|||||||
|
|
||||||
// getRoleRefGvk returns a Gvk in the roleRef field. Return error
|
// getRoleRefGvk returns a Gvk in the roleRef field. Return error
|
||||||
// if the roleRef, roleRef/apiGroup or roleRef/kind is missing.
|
// if the roleRef, roleRef/apiGroup or roleRef/kind is missing.
|
||||||
func getRoleRefGvk(res json.Marshaler) (*resid.Gvk, error) {
|
func getRoleRefGvk(n *yaml.RNode) (*resid.Gvk, error) {
|
||||||
n, err := filtersutil.GetRNode(res)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
roleRef, err := n.Pipe(yaml.Lookup("roleRef"))
|
roleRef, err := n.Pipe(yaml.Lookup("roleRef"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -276,7 +270,7 @@ func (f Filter) roleRefFilter() sieveFunc {
|
|||||||
if !strings.HasSuffix(f.NameFieldToUpdate.Path, "roleRef/name") {
|
if !strings.HasSuffix(f.NameFieldToUpdate.Path, "roleRef/name") {
|
||||||
return acceptAll
|
return acceptAll
|
||||||
}
|
}
|
||||||
roleRefGvk, err := getRoleRefGvk(f.Referrer)
|
roleRefGvk, err := getRoleRefGvk(f.Referrer.AsRNode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return acceptAll
|
return acceptAll
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ map:
|
|||||||
}
|
}
|
||||||
tc.filter.Referrer = referrer
|
tc.filter.Referrer = referrer
|
||||||
|
|
||||||
resMapFactory := resmap.NewFactory(factory, nil)
|
resMapFactory := resmap.NewFactory(factory)
|
||||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||||
tc.originalNames, []byte(tc.candidates))
|
tc.originalNames, []byte(tc.candidates))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -334,7 +334,7 @@ metadata:
|
|||||||
}
|
}
|
||||||
tc.filter.Referrer = referrer
|
tc.filter.Referrer = referrer
|
||||||
|
|
||||||
resMapFactory := resmap.NewFactory(factory, nil)
|
resMapFactory := resmap.NewFactory(factory)
|
||||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||||
tc.originalNames, []byte(tc.candidates))
|
tc.originalNames, []byte(tc.candidates))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -751,7 +751,7 @@ ref:
|
|||||||
}
|
}
|
||||||
tc.filter.Referrer = referrer
|
tc.filter.Referrer = referrer
|
||||||
|
|
||||||
resMapFactory := resmap.NewFactory(factory, nil)
|
resMapFactory := resmap.NewFactory(factory)
|
||||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||||
tc.originalNames, []byte(tc.candidates))
|
tc.originalNames, []byte(tc.candidates))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ func (ns Filter) roleBindingHack(obj *yaml.RNode, meta yaml.ResourceMeta) error
|
|||||||
|
|
||||||
// add the namespace to each "subject" with name: default
|
// add the namespace to each "subject" with name: default
|
||||||
err = obj.VisitElements(func(o *yaml.RNode) error {
|
err = obj.VisitElements(func(o *yaml.RNode) error {
|
||||||
// copied from kunstruct based kustomize NamespaceTransformer plugin
|
|
||||||
// The only case we need to force the namespace
|
// The only case we need to force the namespace
|
||||||
// if for the "service account". "default" is
|
// if for the "service account". "default" is
|
||||||
// kind of hardcoded here for right now.
|
// kind of hardcoded here for right now.
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package patchstrategicmerge
|
package patchstrategicmerge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
|
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
|
||||||
@@ -29,7 +28,7 @@ func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !konfig.FlagEnableKyamlDefaultValue || r != nil {
|
if r != nil {
|
||||||
result = append(result, r)
|
result = append(result, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -723,12 +723,12 @@ spec:
|
|||||||
- name: consul
|
- name: consul
|
||||||
image: "dashicorp/consul:1.9.1"
|
image: "dashicorp/consul:1.9.1"
|
||||||
ports:
|
ports:
|
||||||
|
- containerPort: 8500
|
||||||
|
name: http
|
||||||
- containerPort: 8301
|
- containerPort: 8301
|
||||||
protocol: "TCP"
|
protocol: "TCP"
|
||||||
- containerPort: 8301
|
- containerPort: 8301
|
||||||
protocol: "UDP"
|
protocol: "UDP"
|
||||||
- containerPort: 8500
|
|
||||||
name: http
|
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
4
api/filters/replacement/doc.go
Normal file
4
api/filters/replacement/doc.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Package replacement contains a kio.Filter implementation of the kustomize
|
||||||
|
// replacement transformer (accepts sources and looks for targets to replace
|
||||||
|
// their values with values from the sources).
|
||||||
|
package replacement
|
||||||
68
api/filters/replacement/example_test.go
Normal file
68
api/filters/replacement/example_test.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright 2021 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package replacement
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleFilter() {
|
||||||
|
f := Filter{}
|
||||||
|
err := yaml.Unmarshal([]byte(`
|
||||||
|
replacements:
|
||||||
|
- source:
|
||||||
|
kind: Foo2
|
||||||
|
fieldPath: spec.replicas
|
||||||
|
targets:
|
||||||
|
- select:
|
||||||
|
kind: Foo1
|
||||||
|
fieldPaths:
|
||||||
|
- spec.replicas`), &f)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = kio.Pipeline{
|
||||||
|
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo1
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
---
|
||||||
|
apiVersion: example.com/v1
|
||||||
|
kind: Foo2
|
||||||
|
metadata:
|
||||||
|
name: instance
|
||||||
|
spec:
|
||||||
|
replicas: 99
|
||||||
|
`)}},
|
||||||
|
Filters: []kio.Filter{f},
|
||||||
|
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||||
|
}.Execute()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// apiVersion: example.com/v1
|
||||||
|
// kind: Foo1
|
||||||
|
// metadata:
|
||||||
|
// name: instance
|
||||||
|
// spec:
|
||||||
|
// replicas: 99
|
||||||
|
// ---
|
||||||
|
// apiVersion: example.com/v1
|
||||||
|
// kind: Foo2
|
||||||
|
// metadata:
|
||||||
|
// name: instance
|
||||||
|
// spec:
|
||||||
|
// replicas: 99
|
||||||
|
}
|
||||||
185
api/filters/replacement/replacement.go
Normal file
185
api/filters/replacement/replacement.go
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
// Copyright 2021 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package replacement
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Filter struct {
|
||||||
|
Replacements []types.Replacement
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter replaces values of targets with values from sources
|
||||||
|
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
|
for _, r := range f.Replacements {
|
||||||
|
if r.Source == nil || r.Targets == nil {
|
||||||
|
return nil, fmt.Errorf("replacements must specify a source and at least one target")
|
||||||
|
}
|
||||||
|
value, err := getReplacement(nodes, &r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodes, err = applyReplacement(nodes, value, r.Targets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targets []*types.TargetSelector) ([]*yaml.RNode, error) {
|
||||||
|
for _, t := range targets {
|
||||||
|
if t.Select == nil {
|
||||||
|
return nil, fmt.Errorf("target must specify resources to select")
|
||||||
|
}
|
||||||
|
if len(t.FieldPaths) == 0 {
|
||||||
|
t.FieldPaths = []string{types.DefaultReplacementFieldPath}
|
||||||
|
}
|
||||||
|
for _, n := range nodes {
|
||||||
|
nodeId := getKrmId(n)
|
||||||
|
if t.Select.KrmId.Match(nodeId) && !rejectId(t.Reject, nodeId) {
|
||||||
|
err := applyToNode(n, value, t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rejectId(rejects []*types.Selector, nodeId *types.KrmId) bool {
|
||||||
|
for _, r := range rejects {
|
||||||
|
if r.KrmId.Match(nodeId) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelector) error {
|
||||||
|
for _, fp := range target.FieldPaths {
|
||||||
|
fieldPath := strings.Split(fp, ".")
|
||||||
|
var t *yaml.RNode
|
||||||
|
var err error
|
||||||
|
if target.Options != nil && target.Options.Create {
|
||||||
|
t, err = node.Pipe(yaml.LookupCreate(value.YNode().Kind, fieldPath...))
|
||||||
|
} else {
|
||||||
|
t, err = node.Pipe(yaml.Lookup(fieldPath...))
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
if err = setTargetValue(target.Options, t, value); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTargetValue(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error {
|
||||||
|
if options != nil && options.Delimiter != "" {
|
||||||
|
|
||||||
|
if t.YNode().Kind != yaml.ScalarNode {
|
||||||
|
return fmt.Errorf("delimiter option can only be used with scalar nodes")
|
||||||
|
}
|
||||||
|
|
||||||
|
tv := strings.Split(t.YNode().Value, options.Delimiter)
|
||||||
|
v := yaml.GetValue(value)
|
||||||
|
// TODO: Add a way to remove an element
|
||||||
|
switch {
|
||||||
|
case options.Index < 0: // prefix
|
||||||
|
tv = append([]string{v}, tv...)
|
||||||
|
case options.Index >= len(tv): // suffix
|
||||||
|
tv = append(tv, v)
|
||||||
|
default: // replace an element
|
||||||
|
tv[options.Index] = v
|
||||||
|
}
|
||||||
|
value.YNode().Value = strings.Join(tv, options.Delimiter)
|
||||||
|
}
|
||||||
|
t.SetYNode(value.YNode())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getReplacement(nodes []*yaml.RNode, r *types.Replacement) (*yaml.RNode, error) {
|
||||||
|
source, err := selectSourceNode(nodes, r.Source)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Source.FieldPath == "" {
|
||||||
|
r.Source.FieldPath = types.DefaultReplacementFieldPath
|
||||||
|
}
|
||||||
|
fieldPath := strings.Split(r.Source.FieldPath, ".")
|
||||||
|
|
||||||
|
rn, err := source.Pipe(yaml.Lookup(fieldPath...))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !rn.IsNilOrEmpty() {
|
||||||
|
return getRefinedValue(r.Source.Options, rn)
|
||||||
|
}
|
||||||
|
return rn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode, error) {
|
||||||
|
if options == nil || options.Delimiter == "" {
|
||||||
|
return rn, nil
|
||||||
|
}
|
||||||
|
if rn.YNode().Kind != yaml.ScalarNode {
|
||||||
|
return nil, fmt.Errorf("delimiter option can only be used with scalar nodes")
|
||||||
|
}
|
||||||
|
value := strings.Split(yaml.GetValue(rn), options.Delimiter)
|
||||||
|
if options.Index >= len(value) || options.Index < 0 {
|
||||||
|
return nil, fmt.Errorf("options.index %d is out of bounds for value %s", options.Index, yaml.GetValue(rn))
|
||||||
|
}
|
||||||
|
n := rn.Copy()
|
||||||
|
n.YNode().Value = value[options.Index]
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// selectSourceNode finds the node that matches the selector, returning
|
||||||
|
// an error if multiple or none are found
|
||||||
|
func selectSourceNode(nodes []*yaml.RNode, selector *types.SourceSelector) (*yaml.RNode, error) {
|
||||||
|
var matches []*yaml.RNode
|
||||||
|
for _, n := range nodes {
|
||||||
|
if selector.KrmId.Match(getKrmId(n)) {
|
||||||
|
if len(matches) > 0 {
|
||||||
|
return nil, fmt.Errorf("more than one match for source %v", selector)
|
||||||
|
}
|
||||||
|
matches = append(matches, n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(matches) == 0 {
|
||||||
|
return nil, fmt.Errorf("found no matches for source %v", selector)
|
||||||
|
}
|
||||||
|
return matches[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKrmId(n *yaml.RNode) *types.KrmId {
|
||||||
|
ns, err := n.GetNamespace()
|
||||||
|
if err != nil {
|
||||||
|
// Resource has no metadata (no apiVersion, kind, nor metadata field).
|
||||||
|
// In this case, it cannot be selected.
|
||||||
|
return &types.KrmId{}
|
||||||
|
}
|
||||||
|
apiVersion := n.Field(yaml.APIVersionField)
|
||||||
|
var group, version string
|
||||||
|
if apiVersion != nil {
|
||||||
|
group, version = resid.ParseGroupVersion(yaml.GetValue(apiVersion.Value))
|
||||||
|
}
|
||||||
|
return &types.KrmId{
|
||||||
|
Gvk: resid.Gvk{Group: group, Version: version, Kind: n.GetKind()},
|
||||||
|
Name: n.GetName(),
|
||||||
|
Namespace: ns,
|
||||||
|
}
|
||||||
|
}
|
||||||
1365
api/filters/replacement/replacement_test.go
Normal file
1365
api/filters/replacement/replacement_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,13 +6,11 @@ require (
|
|||||||
github.com/evanphx/json-patch v4.5.0+incompatible
|
github.com/evanphx/json-patch v4.5.0+incompatible
|
||||||
github.com/go-errors/errors v1.0.1
|
github.com/go-errors/errors v1.0.1
|
||||||
github.com/go-openapi/spec v0.19.5
|
github.com/go-openapi/spec v0.19.5
|
||||||
github.com/google/go-cmp v0.4.0
|
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||||
github.com/imdario/mergo v0.3.5
|
github.com/imdario/mergo v0.3.7
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/stretchr/testify v1.4.0
|
github.com/stretchr/testify v1.4.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
sigs.k8s.io/kustomize/kyaml v0.10.17
|
||||||
sigs.k8s.io/kustomize/kyaml v0.10.15
|
|
||||||
sigs.k8s.io/yaml v1.2.0
|
sigs.k8s.io/yaml v1.2.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -112,8 +112,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg
|
|||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
|
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
||||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
@@ -242,7 +242,6 @@ golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGm
|
|||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
@@ -265,7 +264,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI=
|
sigs.k8s.io/kustomize/kyaml v0.10.17 h1:4zrV0ym5AYa0e512q7K3Wp1u7mzoWW0xR3UHJcGWGIg=
|
||||||
sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
|
sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
|
||||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ func SortArrayAndComputeHash(s []string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return Encode(Hash(string(data)))
|
return encode(hex256(string(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copied from https://github.com/kubernetes/kubernetes
|
// Copied from https://github.com/kubernetes/kubernetes
|
||||||
// /blob/master/pkg/kubectl/util/hash/hash.go
|
// /blob/master/pkg/kubectl/util/hash/hash.go
|
||||||
func Encode(hex string) (string, error) {
|
func encode(hex string) (string, error) {
|
||||||
if len(hex) < 10 {
|
if len(hex) < 10 {
|
||||||
return "", fmt.Errorf(
|
return "", fmt.Errorf(
|
||||||
"input length must be at least 10")
|
"input length must be at least 10")
|
||||||
@@ -48,23 +48,18 @@ func Encode(hex string) (string, error) {
|
|||||||
return string(enc), nil
|
return string(enc), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash returns the hex form of the sha256 of the argument.
|
// hex256 returns the hex form of the sha256 of the argument.
|
||||||
func Hash(data string) string {
|
func hex256(data string) string {
|
||||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashRNode returns the hash value of input RNode
|
// Hasher computes the hash of an RNode.
|
||||||
func HashRNode(node *yaml.RNode) (string, error) {
|
type Hasher struct{}
|
||||||
// get node kind
|
|
||||||
kindNode, err := node.Pipe(yaml.FieldMatcher{Name: "kind"})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
kind := kindNode.YNode().Value
|
|
||||||
|
|
||||||
// calculate hash for different kinds
|
// Hash returns a hash of the argument.
|
||||||
encoded := ""
|
func (h *Hasher) Hash(node *yaml.RNode) (r string, err error) {
|
||||||
switch kind {
|
var encoded string
|
||||||
|
switch node.GetKind() {
|
||||||
case "ConfigMap":
|
case "ConfigMap":
|
||||||
encoded, err = encodeConfigMap(node)
|
encoded, err = encodeConfigMap(node)
|
||||||
case "Secret":
|
case "Secret":
|
||||||
@@ -77,10 +72,11 @@ func HashRNode(node *yaml.RNode) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return Encode(Hash(encoded))
|
return encode(hex256(encoded))
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNodeValues(node *yaml.RNode, paths []string) (map[string]interface{}, error) {
|
func getNodeValues(
|
||||||
|
node *yaml.RNode, paths []string) (map[string]interface{}, error) {
|
||||||
values := make(map[string]interface{})
|
values := make(map[string]interface{})
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
vn, err := node.Pipe(yaml.Lookup(p))
|
vn, err := node.Pipe(yaml.Lookup(p))
|
||||||
@@ -117,8 +113,11 @@ func encodeConfigMap(node *yaml.RNode) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
m := map[string]interface{}{"kind": "ConfigMap", "name": values["metadata/name"],
|
m := map[string]interface{}{
|
||||||
"data": values["data"]}
|
"kind": "ConfigMap",
|
||||||
|
"name": values["metadata/name"],
|
||||||
|
"data": values["data"],
|
||||||
|
}
|
||||||
if _, ok := values["binaryData"].(map[string]interface{}); ok {
|
if _, ok := values["binaryData"].(map[string]interface{}); ok {
|
||||||
m["binaryData"] = values["binaryData"]
|
m["binaryData"] = values["binaryData"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ func TestSortArrayAndComputeHash(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHash(t *testing.T) {
|
func Test_hex256(t *testing.T) {
|
||||||
// hash the empty string to be sure that sha256 is being used
|
// hash the empty string to be sure that sha256 is being used
|
||||||
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
sum := Hash("")
|
sum := hex256("")
|
||||||
if expect != sum {
|
if expect != sum {
|
||||||
t.Errorf("expected hash %q but got %q", expect, sum)
|
t.Errorf("expected hash %q but got %q", expect, sum)
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ kind: ConfigMap`, "6ct58987ht", ""},
|
|||||||
{"one key", `
|
{"one key", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
data:
|
data:
|
||||||
one: ""`, "9g67k2htb6", ""},
|
one: ""`, "9g67k2htb6", ""},
|
||||||
// three keys (tests sorting order)
|
// three keys (tests sorting order)
|
||||||
{"three keys", `
|
{"three keys", `
|
||||||
@@ -93,17 +93,17 @@ data:
|
|||||||
binaryData:
|
binaryData:
|
||||||
two: ""`, "698h7c7t9m", ""},
|
two: ""`, "698h7c7t9m", ""},
|
||||||
}
|
}
|
||||||
|
h := &Hasher{}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
node, err := yaml.Parse(c.cmYaml)
|
node, err := yaml.Parse(c.cmYaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
h, err := HashRNode(node)
|
hashed, err := h.Hash(node)
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c.hash != h {
|
if c.hash != hashed {
|
||||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,35 +154,34 @@ type: my-type
|
|||||||
data:
|
data:
|
||||||
one: ""`, "74bd68bm66", ""},
|
one: ""`, "74bd68bm66", ""},
|
||||||
}
|
}
|
||||||
|
h := &Hasher{}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
node, err := yaml.Parse(c.secretYaml)
|
node, err := yaml.Parse(c.secretYaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
h, err := HashRNode(node)
|
hashed, err := h.Hash(node)
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
if SkipRest(t, c.desc, err, c.err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c.hash != h {
|
if c.hash != hashed {
|
||||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnstructuredHash(t *testing.T) {
|
func TestBasicHash(t *testing.T) {
|
||||||
cases := []struct {
|
cases := map[string]struct {
|
||||||
desc string
|
res string
|
||||||
unstructured string
|
hash string
|
||||||
hash string
|
err string
|
||||||
err string
|
|
||||||
}{
|
}{
|
||||||
{"minimal", `
|
"minimal": {`
|
||||||
apiVersion: test/v1
|
apiVersion: test/v1
|
||||||
kind: TestResource
|
kind: TestResource
|
||||||
metadata:
|
metadata:
|
||||||
name: my-resource`, "244782mkb7", ""},
|
name: my-resource`, "244782mkb7", ""},
|
||||||
{"with spec", `
|
"with spec": {`
|
||||||
apiVersion: test/v1
|
apiVersion: test/v1
|
||||||
kind: TestResource
|
kind: TestResource
|
||||||
metadata:
|
metadata:
|
||||||
@@ -191,19 +190,22 @@ spec:
|
|||||||
foo: 1
|
foo: 1
|
||||||
bar: abc`, "59m2mdccg4", ""},
|
bar: abc`, "59m2mdccg4", ""},
|
||||||
}
|
}
|
||||||
|
h := &Hasher{}
|
||||||
for _, c := range cases {
|
for n := range cases {
|
||||||
node, err := yaml.Parse(c.unstructured)
|
c := cases[n]
|
||||||
if err != nil {
|
t.Run(n, func(t *testing.T) {
|
||||||
t.Fatal(err)
|
node, err := yaml.Parse(c.res)
|
||||||
}
|
if err != nil {
|
||||||
h, err := HashRNode(node)
|
t.Fatal(err)
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
}
|
||||||
continue
|
hashed, err := h.Hash(node)
|
||||||
}
|
if SkipRest(t, n, err, c.err) {
|
||||||
if c.hash != h {
|
return
|
||||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
}
|
||||||
}
|
if c.hash != hashed {
|
||||||
|
t.Errorf("case %q, expect hash %q but got %q", n, c.hash, h)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +224,7 @@ kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""},
|
|||||||
{"one key", `
|
{"one key", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
data:
|
data:
|
||||||
one: ""`, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
one: ""`, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
||||||
// three keys (tests sorting order)
|
// three keys (tests sorting order)
|
||||||
{"three keys", `
|
{"three keys", `
|
||||||
@@ -334,9 +336,10 @@ data:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SkipRest returns true if there was a non-nil error or if we expected an error that didn't happen,
|
// SkipRest returns true if there was a non-nil error or if we expected an
|
||||||
// and logs the appropriate error on the test object.
|
// error that didn't happen, and logs the appropriate error on the test object.
|
||||||
// The return value indicates whether we should skip the rest of the test case due to the error result.
|
// The return value indicates whether we should skip the rest of the test case
|
||||||
|
// due to the error result.
|
||||||
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
|
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if len(contains) == 0 {
|
if len(contains) == 0 {
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
package ifc
|
package ifc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validator provides functions to validate annotations and labels
|
// Validator provides functions to validate annotations and labels
|
||||||
@@ -38,92 +38,10 @@ type Loader interface {
|
|||||||
Cleanup() error
|
Cleanup() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kunstructured represents a Kubernetes Resource Model object.
|
// KustHasher returns a hash of the argument
|
||||||
type Kunstructured interface {
|
|
||||||
// Several uses.
|
|
||||||
Copy() Kunstructured
|
|
||||||
|
|
||||||
// GetAnnotations returns the k8s annotations.
|
|
||||||
GetAnnotations() map[string]string
|
|
||||||
|
|
||||||
// GetData returns a top-level "data" field, as in a ConfigMap.
|
|
||||||
GetDataMap() map[string]string
|
|
||||||
|
|
||||||
// GetData returns a top-level "binaryData" field, as in a ConfigMap.
|
|
||||||
GetBinaryDataMap() map[string]string
|
|
||||||
|
|
||||||
// Used by ResAccumulator and ReplacementTransformer.
|
|
||||||
GetFieldValue(string) (interface{}, error)
|
|
||||||
|
|
||||||
// Used by Resource.OrgId
|
|
||||||
GetGvk() resid.Gvk
|
|
||||||
|
|
||||||
// Used by resource.Factory.SliceFromBytes
|
|
||||||
GetKind() string
|
|
||||||
|
|
||||||
// GetLabels returns the k8s labels.
|
|
||||||
GetLabels() map[string]string
|
|
||||||
|
|
||||||
// Used by Resource.CurId and resource factory.
|
|
||||||
GetName() string
|
|
||||||
|
|
||||||
// Used by special case code in
|
|
||||||
// ResMap.SubsetThatCouldBeReferencedByResource
|
|
||||||
GetSlice(path string) ([]interface{}, error)
|
|
||||||
|
|
||||||
// GetString returns the value of a string field.
|
|
||||||
// Used by Resource.GetNamespace
|
|
||||||
GetString(string) (string, error)
|
|
||||||
|
|
||||||
// Several uses.
|
|
||||||
Map() (map[string]interface{}, error)
|
|
||||||
|
|
||||||
// Used by Resource.AsYAML and Resource.String
|
|
||||||
MarshalJSON() ([]byte, error)
|
|
||||||
|
|
||||||
// Used by resWrangler.Select
|
|
||||||
MatchesAnnotationSelector(selector string) (bool, error)
|
|
||||||
|
|
||||||
// Used by resWrangler.Select
|
|
||||||
MatchesLabelSelector(selector string) (bool, error)
|
|
||||||
|
|
||||||
// SetAnnotations replaces the k8s annotations.
|
|
||||||
SetAnnotations(map[string]string)
|
|
||||||
|
|
||||||
// SetDataMap sets a top-level "data" field, as in a ConfigMap.
|
|
||||||
SetDataMap(map[string]string)
|
|
||||||
|
|
||||||
// SetDataMap sets a top-level "binaryData" field, as in a ConfigMap.
|
|
||||||
SetBinaryDataMap(map[string]string)
|
|
||||||
// Used by PatchStrategicMergeTransformer.
|
|
||||||
SetGvk(resid.Gvk)
|
|
||||||
|
|
||||||
// SetLabels replaces the k8s labels.
|
|
||||||
SetLabels(map[string]string)
|
|
||||||
|
|
||||||
// SetName changes the name.
|
|
||||||
SetName(string)
|
|
||||||
|
|
||||||
// SetNamespace changes the namespace.
|
|
||||||
SetNamespace(string)
|
|
||||||
|
|
||||||
// Needed, for now, by kyaml/filtersutil.ApplyToJSON.
|
|
||||||
UnmarshalJSON([]byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// KunstructuredFactory makes instances of Kunstructured.
|
|
||||||
type KunstructuredFactory interface {
|
|
||||||
SliceFromBytes([]byte) ([]Kunstructured, error)
|
|
||||||
FromMap(m map[string]interface{}) Kunstructured
|
|
||||||
Hasher() KunstructuredHasher
|
|
||||||
MakeConfigMap(kvLdr KvLoader, args *types.ConfigMapArgs) (Kunstructured, error)
|
|
||||||
MakeSecret(kvLdr KvLoader, args *types.SecretArgs) (Kunstructured, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// KunstructuredHasher returns a hash of the argument
|
|
||||||
// or an error.
|
// or an error.
|
||||||
type KunstructuredHasher interface {
|
type KustHasher interface {
|
||||||
Hash(Kunstructured) (string, error)
|
Hash(*yaml.RNode) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// See core.v1.SecretTypeOpaque
|
// See core.v1.SecretTypeOpaque
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ type OpenAPIDefinition struct {
|
|||||||
Dependencies []string
|
Dependencies []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type myProperties map[string]spec.Schema
|
type myProperties = map[string]spec.Schema
|
||||||
type nameToApiMap map[string]OpenAPIDefinition
|
type nameToApiMap map[string]OpenAPIDefinition
|
||||||
|
|
||||||
// LoadConfigFromCRDs parse CRD schemas from paths into a TransformerConfig
|
// LoadConfigFromCRDs parse CRD schemas from paths into a TransformerConfig
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
"sigs.k8s.io/kustomize/api/provider"
|
"sigs.k8s.io/kustomize/api/provider"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
@@ -521,17 +520,9 @@ func TestNameReferenceUnhappyRun(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).ResMap(),
|
}).ResMap(),
|
||||||
// TODO(#3304): DECISION - kyaml better; not a bug.
|
expectedErr: `updating name reference in 'rules/resourceNames' field of ` +
|
||||||
expectedErr: konfig.IfApiMachineryElseKyaml(
|
`'rbac.authorization.k8s.io_v1_ClusterRole|~X|cr'` +
|
||||||
`updating name reference in 'rules/resourceNames' field of `+
|
`: considering field 'rules/resourceNames' of object
|
||||||
`'rbac.authorization.k8s.io_v1_ClusterRole|~X|cr'`+
|
|
||||||
`: considering field 'rules/resourceNames' of object
|
|
||||||
{"apiVersion": "rbac.authorization.k8s.io/v1", "kind": "ClusterRole", "metadata": {
|
|
||||||
"name": "cr"}, "rules": [{"resourceNames": {"foo": "bar"}, "resources": ["secrets"]}]}
|
|
||||||
: visit traversal on path: [resourceNames]: path config error; no 'name' field in node`,
|
|
||||||
`updating name reference in 'rules/resourceNames' field of `+
|
|
||||||
`'rbac.authorization.k8s.io_v1_ClusterRole|~X|cr'`+
|
|
||||||
`: considering field 'rules/resourceNames' of object
|
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
metadata:
|
metadata:
|
||||||
@@ -541,7 +532,7 @@ rules:
|
|||||||
foo: bar
|
foo: bar
|
||||||
resources:
|
resources:
|
||||||
- secrets
|
- secrets
|
||||||
: visit traversal on path: [resourceNames]: path config error; no 'name' field in node`)},
|
: visit traversal on path: [resourceNames]: path config error; no 'name' field in node`},
|
||||||
}
|
}
|
||||||
|
|
||||||
nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
|
nrt := newNameReferenceTransformer(builtinconfig.MakeDefaultConfig().NameReference)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||||
@@ -121,12 +120,7 @@ func TestRefVarTransformer(t *testing.T) {
|
|||||||
"slice": []interface{}{5}, // noticeably *not* a []string
|
"slice": []interface{}{5}, // noticeably *not* a []string
|
||||||
}}).ResMap(),
|
}}).ResMap(),
|
||||||
},
|
},
|
||||||
// TODO(#3304): DECISION - kyaml better; not a bug.
|
errMessage: `considering field 'data/slice' of object
|
||||||
errMessage: konfig.IfApiMachineryElseKyaml(
|
|
||||||
`considering field 'data/slice' of object
|
|
||||||
{"apiVersion": "v1", "data": {"slice": [5]}, "kind": "ConfigMap", "metadata": {"name": "cm1"}}
|
|
||||||
: invalid value type expect a string`,
|
|
||||||
`considering field 'data/slice' of object
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
data:
|
data:
|
||||||
slice:
|
slice:
|
||||||
@@ -135,7 +129,6 @@ kind: ConfigMap
|
|||||||
metadata:
|
metadata:
|
||||||
name: cm1
|
name: cm1
|
||||||
: invalid value type expect a string`,
|
: invalid value type expect a string`,
|
||||||
),
|
|
||||||
},
|
},
|
||||||
"var replacement in nil": {
|
"var replacement in nil": {
|
||||||
given: given{
|
given: given{
|
||||||
|
|||||||
@@ -352,6 +352,7 @@ func TestResolveVarsWithNoambiguation(t *testing.T) {
|
|||||||
"metadata": map[string]interface{}{
|
"metadata": map[string]interface{}{
|
||||||
"name": "sub-backendOne",
|
"name": "sub-backendOne",
|
||||||
"annotations": map[string]interface{}{
|
"annotations": map[string]interface{}{
|
||||||
|
"config.kubernetes.io/previousKinds": "Service",
|
||||||
"config.kubernetes.io/previousNames": "backendOne",
|
"config.kubernetes.io/previousNames": "backendOne",
|
||||||
"config.kubernetes.io/previousNamespaces": "default",
|
"config.kubernetes.io/previousNamespaces": "default",
|
||||||
"config.kubernetes.io/prefixes": "sub-",
|
"config.kubernetes.io/prefixes": "sub-",
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package conflict
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
type cdFactory struct{}
|
|
||||||
|
|
||||||
var _ resource.ConflictDetectorFactory = &cdFactory{}
|
|
||||||
|
|
||||||
// NewFactory returns a new conflict detector factory.
|
|
||||||
func NewFactory() resource.ConflictDetectorFactory {
|
|
||||||
return &cdFactory{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns an instance of smPatchMergeOnlyDetector.
|
|
||||||
func (c cdFactory) New(_ resid.Gvk) (resource.ConflictDetector, error) {
|
|
||||||
return &smPatchMergeOnlyDetector{}, nil
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package conflict
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// smPatchMergeOnlyDetector ignores conflicts,
|
|
||||||
// but does real strategic merge patching.
|
|
||||||
// This is part of an effort to eliminate dependence on
|
|
||||||
// apimachinery package to allow kustomize integration
|
|
||||||
// into kubectl (#2506 and #1500)
|
|
||||||
type smPatchMergeOnlyDetector struct{}
|
|
||||||
|
|
||||||
var _ resource.ConflictDetector = &smPatchMergeOnlyDetector{}
|
|
||||||
|
|
||||||
func (c *smPatchMergeOnlyDetector) HasConflict(
|
|
||||||
_, _ *resource.Resource) (bool, error) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// There's at least one case that doesn't work. Suppose one has a
|
|
||||||
// Deployment with a volume with the bizarre "emptyDir: {}" entry.
|
|
||||||
// If you want to get rid of this entry via a patch containing
|
|
||||||
// the entry "emptyDir: null", then the following won't work,
|
|
||||||
// because null entries are eliminated.
|
|
||||||
func (c *smPatchMergeOnlyDetector) MergePatches(
|
|
||||||
r, patch *resource.Resource) (*resource.Resource, error) {
|
|
||||||
err := r.ApplySmPatch(patch)
|
|
||||||
return r, err
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
server "sigs.k8s.io/kustomize/api/internal/crawl/backend"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
server "sigs.k8s.io/kustomize/api/internal/crawl/backend"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func (gc githubCrawler) Crawl(ctx context.Context,
|
|||||||
output chan<- crawler.CrawledDocument, seen utils.SeenMap) error {
|
output chan<- crawler.CrawledDocument, seen utils.SeenMap) error {
|
||||||
|
|
||||||
ranges := []RangeWithin{
|
ranges := []RangeWithin{
|
||||||
RangeWithin{
|
{
|
||||||
start: uint64(0),
|
start: uint64(0),
|
||||||
end: githubMaxFileSize,
|
end: githubMaxFileSize,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -96,6 +96,6 @@ func TestRangeSizes(t *testing.T) {
|
|||||||
returnedResult := RangeSizes(s)
|
returnedResult := RangeSizes(s)
|
||||||
expectedResult := RangeWithin{uint64(2365), uint64(10000)}
|
expectedResult := RangeWithin{uint64(2365), uint64(10000)}
|
||||||
if !reflect.DeepEqual(returnedResult, expectedResult) {
|
if !reflect.DeepEqual(returnedResult, expectedResult) {
|
||||||
t.Errorf("RangeSizes expected (%v), got (%v)",expectedResult, returnedResult)
|
t.Errorf("RangeSizes expected (%v), got (%v)", expectedResult, returnedResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg
|
|||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
@@ -269,7 +269,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI=
|
sigs.k8s.io/kustomize/kyaml v0.10.17 h1:4zrV0ym5AYa0e512q7K3Wp1u7mzoWW0xR3UHJcGWGIg=
|
||||||
sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
|
sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
|
||||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import (
|
|||||||
. "sigs.k8s.io/kustomize/api/internal/plugins/execplugin"
|
. "sigs.k8s.io/kustomize/api/internal/plugins/execplugin"
|
||||||
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/utils"
|
"sigs.k8s.io/kustomize/api/internal/plugins/utils"
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
fLdr "sigs.k8s.io/kustomize/api/loader"
|
fLdr "sigs.k8s.io/kustomize/api/loader"
|
||||||
"sigs.k8s.io/kustomize/api/provider"
|
"sigs.k8s.io/kustomize/api/provider"
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestExecPluginConfig(t *testing.T) {
|
func TestExecPluginConfig(t *testing.T) {
|
||||||
@@ -32,8 +32,7 @@ s/$BAR/bar baz/g
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
pvd := provider.NewDefaultDepProvider()
|
pvd := provider.NewDefaultDepProvider()
|
||||||
rf := resmap.NewFactory(
|
rf := resmap.NewFactory(pvd.GetResourceFactory())
|
||||||
pvd.GetResourceFactory(), pvd.GetConflictDetectorFactory())
|
|
||||||
pluginConfig := rf.RF().FromMap(
|
pluginConfig := rf.RF().FromMap(
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"apiVersion": "someteam.example.com/v1",
|
"apiVersion": "someteam.example.com/v1",
|
||||||
@@ -46,10 +45,13 @@ s/$BAR/bar baz/g
|
|||||||
})
|
})
|
||||||
|
|
||||||
pluginConfig.RemoveBuildAnnotations()
|
pluginConfig.RemoveBuildAnnotations()
|
||||||
p := NewExecPlugin(
|
pc := types.DisabledPluginConfig()
|
||||||
pLdr.AbsolutePluginPath(
|
loader := pLdr.NewLoader(pc, rf, fSys)
|
||||||
konfig.DisabledPluginConfig(),
|
pluginPath, err := loader.AbsolutePluginPath(pluginConfig.OrgId())
|
||||||
pluginConfig.OrgId()))
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
p := NewExecPlugin(pluginPath)
|
||||||
// Not checking to see if the plugin is executable,
|
// Not checking to see if the plugin is executable,
|
||||||
// because this test does not run it.
|
// because this test does not run it.
|
||||||
// This tests only covers sending configuration
|
// This tests only covers sending configuration
|
||||||
@@ -60,7 +62,9 @@ s/$BAR/bar baz/g
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected err: %v", err)
|
t.Fatalf("unexpected err: %v", err)
|
||||||
}
|
}
|
||||||
p.Config(resmap.NewPluginHelpers(ldr, pvd.GetFieldValidator(), rf), yaml)
|
p.Config(
|
||||||
|
resmap.NewPluginHelpers(ldr, pvd.GetFieldValidator(), rf, pc),
|
||||||
|
yaml)
|
||||||
|
|
||||||
expected := "someteam.example.com/v1/sedtransformer/SedTransformer"
|
expected := "someteam.example.com/v1/sedtransformer/SedTransformer"
|
||||||
if !strings.HasSuffix(p.Path(), expected) {
|
if !strings.HasSuffix(p.Path(), expected) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers"
|
"sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers"
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/execplugin"
|
"sigs.k8s.io/kustomize/api/internal/plugins/execplugin"
|
||||||
@@ -29,11 +30,21 @@ import (
|
|||||||
type Loader struct {
|
type Loader struct {
|
||||||
pc *types.PluginConfig
|
pc *types.PluginConfig
|
||||||
rf *resmap.Factory
|
rf *resmap.Factory
|
||||||
|
fs filesys.FileSystem
|
||||||
|
|
||||||
|
// absolutePluginHome caches the location of a valid plugin root directory.
|
||||||
|
// It should only be set once the directory's existence has been confirmed.
|
||||||
|
absolutePluginHome string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLoader(
|
func NewLoader(
|
||||||
pc *types.PluginConfig, rf *resmap.Factory) *Loader {
|
pc *types.PluginConfig, rf *resmap.Factory, fs filesys.FileSystem) *Loader {
|
||||||
return &Loader{pc: pc, rf: rf}
|
return &Loader{pc: pc, rf: rf, fs: fs}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config provides the global (not plugin specific) PluginConfig data.
|
||||||
|
func (l *Loader) Config() *types.PluginConfig {
|
||||||
|
return l.pc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Loader) LoadGenerators(
|
func (l *Loader) LoadGenerators(
|
||||||
@@ -95,13 +106,47 @@ func relativePluginPath(id resid.ResId) string {
|
|||||||
strings.ToLower(id.Kind))
|
strings.ToLower(id.Kind))
|
||||||
}
|
}
|
||||||
|
|
||||||
func AbsolutePluginPath(pc *types.PluginConfig, id resid.ResId) string {
|
func (l *Loader) AbsolutePluginPath(id resid.ResId) (string, error) {
|
||||||
return filepath.Join(
|
pluginHome, err := l.absPluginHome()
|
||||||
pc.AbsPluginHome, relativePluginPath(id), id.Kind)
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(pluginHome, relativePluginPath(id), id.Kind), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Loader) absolutePluginPath(id resid.ResId) string {
|
// absPluginHome is the home of kustomize Exec and Go plugins.
|
||||||
return AbsolutePluginPath(l.pc, id)
|
// Kustomize plugin configuration files are k8s-style objects
|
||||||
|
// containing the fields 'apiVersion' and 'kind', e.g.
|
||||||
|
// apiVersion: apps/v1
|
||||||
|
// kind: Deployment
|
||||||
|
// kustomize reads plugin configuration data from a file path
|
||||||
|
// specified in the 'generators:' or 'transformers:' field of a
|
||||||
|
// kustomization file. For Exec and Go plugins, kustomize
|
||||||
|
// uses this data to both locate the plugin and configure it.
|
||||||
|
// Each Exec or Go plugin (its code, its tests, its supporting data
|
||||||
|
// files, etc.) must be housed in its own directory at
|
||||||
|
// ${absPluginHome}/${pluginApiVersion}/LOWERCASE(${pluginKind})
|
||||||
|
// where
|
||||||
|
// - ${absPluginHome} is an absolute path, defined below.
|
||||||
|
// - ${pluginApiVersion} is taken from the plugin config file.
|
||||||
|
// - ${pluginKind} is taken from the plugin config file.
|
||||||
|
func (l *Loader) absPluginHome() (string, error) {
|
||||||
|
// External plugins are disabled--return the dummy plugin root.
|
||||||
|
if l.pc.PluginRestrictions != types.PluginRestrictionsNone {
|
||||||
|
return konfig.NoPluginHomeSentinal, nil
|
||||||
|
}
|
||||||
|
// We've already determined plugin home--use the cached value.
|
||||||
|
if l.absolutePluginHome != "" {
|
||||||
|
return l.absolutePluginHome, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check default locations for a valid plugin root, and cache it if found.
|
||||||
|
dir, err := konfig.DefaultAbsPluginHome(l.fs)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
l.absolutePluginHome = dir
|
||||||
|
return l.absolutePluginHome, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isBuiltinPlugin(res *resource.Resource) bool {
|
func isBuiltinPlugin(res *resource.Resource) bool {
|
||||||
@@ -148,7 +193,7 @@ func (l *Loader) loadAndConfigurePlugin(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "marshalling yaml from res %s", res.OrgId())
|
return nil, errors.Wrapf(err, "marshalling yaml from res %s", res.OrgId())
|
||||||
}
|
}
|
||||||
err = c.Config(resmap.NewPluginHelpers(ldr, v, l.rf), yaml)
|
err = c.Config(resmap.NewPluginHelpers(ldr, v, l.rf, l.pc), yaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(
|
return nil, errors.Wrapf(
|
||||||
err, "plugin %s fails configuration", res.OrgId())
|
err, "plugin %s fails configuration", res.OrgId())
|
||||||
@@ -176,10 +221,13 @@ func (l *Loader) loadPlugin(res *resource.Resource) (resmap.Configurable, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Loader) loadExecOrGoPlugin(resId resid.ResId) (resmap.Configurable, error) {
|
func (l *Loader) loadExecOrGoPlugin(resId resid.ResId) (resmap.Configurable, error) {
|
||||||
|
absPluginPath, err := l.AbsolutePluginPath(resId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// First try to load the plugin as an executable.
|
// First try to load the plugin as an executable.
|
||||||
p := execplugin.NewExecPlugin(l.absolutePluginPath(resId))
|
p := execplugin.NewExecPlugin(absPluginPath)
|
||||||
err := p.ErrIfNotExecutable()
|
if err = p.ErrIfNotExecutable(); err == nil {
|
||||||
if err == nil {
|
|
||||||
return p, nil
|
return p, nil
|
||||||
}
|
}
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
@@ -193,7 +241,7 @@ func (l *Loader) loadExecOrGoPlugin(resId resid.ResId) (resmap.Configurable, err
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Failing the above, try loading it as a Go plugin.
|
// Failing the above, try loading it as a Go plugin.
|
||||||
c, err := l.loadGoPlugin(resId)
|
c, err := l.loadGoPlugin(resId, absPluginPath+".so")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -208,12 +256,11 @@ func (l *Loader) loadExecOrGoPlugin(resId resid.ResId) (resmap.Configurable, err
|
|||||||
// as a Loader instance variable. So make it a package variable.
|
// as a Loader instance variable. So make it a package variable.
|
||||||
var registry = make(map[string]resmap.Configurable)
|
var registry = make(map[string]resmap.Configurable)
|
||||||
|
|
||||||
func (l *Loader) loadGoPlugin(id resid.ResId) (resmap.Configurable, error) {
|
func (l *Loader) loadGoPlugin(id resid.ResId, absPath string) (resmap.Configurable, error) {
|
||||||
regId := relativePluginPath(id)
|
regId := relativePluginPath(id)
|
||||||
if c, ok := registry[regId]; ok {
|
if c, ok := registry[regId]; ok {
|
||||||
return copyPlugin(c), nil
|
return copyPlugin(c), nil
|
||||||
}
|
}
|
||||||
absPath := l.absolutePluginPath(id) + ".so"
|
|
||||||
if !utils.FileExists(absPath) {
|
if !utils.FileExists(absPath) {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
"expected file with Go object code at: %s", absPath)
|
"expected file with Go object code at: %s", absPath)
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
. "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
. "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
"sigs.k8s.io/kustomize/api/loader"
|
"sigs.k8s.io/kustomize/api/loader"
|
||||||
"sigs.k8s.io/kustomize/api/provider"
|
"sigs.k8s.io/kustomize/api/provider"
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
@@ -51,11 +50,11 @@ func TestLoader(t *testing.T) {
|
|||||||
BuildGoPlugin("someteam.example.com", "v1", "SomeServiceGenerator")
|
BuildGoPlugin("someteam.example.com", "v1", "SomeServiceGenerator")
|
||||||
defer th.Reset()
|
defer th.Reset()
|
||||||
p := provider.NewDefaultDepProvider()
|
p := provider.NewDefaultDepProvider()
|
||||||
rmF := resmap.NewFactory(
|
rmF := resmap.NewFactory(p.GetResourceFactory())
|
||||||
p.GetResourceFactory(), p.GetConflictDetectorFactory())
|
fsys := filesys.MakeFsInMemory()
|
||||||
fLdr, err := loader.NewLoader(
|
fLdr, err := loader.NewLoader(
|
||||||
loader.RestrictionRootOnly,
|
loader.RestrictionRootOnly,
|
||||||
filesys.Separator, filesys.MakeFsInMemory())
|
filesys.Separator, fsys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -67,11 +66,8 @@ func TestLoader(t *testing.T) {
|
|||||||
for _, behavior := range []types.BuiltinPluginLoadingOptions{
|
for _, behavior := range []types.BuiltinPluginLoadingOptions{
|
||||||
/* types.BploUseStaticallyLinked,
|
/* types.BploUseStaticallyLinked,
|
||||||
types.BploLoadFromFileSys */} {
|
types.BploLoadFromFileSys */} {
|
||||||
c, err := konfig.EnabledPluginConfig(behavior)
|
c := types.EnabledPluginConfig(behavior)
|
||||||
if err != nil {
|
pLdr := NewLoader(c, rmF, fsys)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
pLdr := NewLoader(c, rmF)
|
|
||||||
if pLdr == nil {
|
if pLdr == nil {
|
||||||
t.Fatal("expect non-nil loader")
|
t.Fatal("expect non-nil loader")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func GoBin() string {
|
|||||||
// has her ${g}/${v}/$lower(${k})/${k}.go files.
|
// has her ${g}/${v}/$lower(${k})/${k}.go files.
|
||||||
func DeterminePluginSrcRoot(fSys filesys.FileSystem) (string, error) {
|
func DeterminePluginSrcRoot(fSys filesys.FileSystem) (string, error) {
|
||||||
return konfig.FirstDirThatExistsElseError(
|
return konfig.FirstDirThatExistsElseError(
|
||||||
"source directory", fSys, []konfig.NotedFunc{
|
"plugin src root", fSys, []konfig.NotedFunc{
|
||||||
{
|
{
|
||||||
Note: "relative to unit test",
|
Note: "relative to unit test",
|
||||||
F: func() string {
|
F: func() string {
|
||||||
|
|||||||
@@ -443,7 +443,10 @@ func (kt *KustTarget) configureBuiltinPlugin(
|
|||||||
err, "builtin %s marshal", bpt)
|
err, "builtin %s marshal", bpt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = p.Config(resmap.NewPluginHelpers(kt.ldr, kt.validator, kt.rFactory), y)
|
err = p.Config(
|
||||||
|
resmap.NewPluginHelpers(
|
||||||
|
kt.ldr, kt.validator, kt.rFactory, kt.pLdr.Config()),
|
||||||
|
y)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(
|
return errors.Wrapf(
|
||||||
err, "trouble configuring builtin %s with config: `\n%s`", bpt, string(y))
|
err, "trouble configuring builtin %s with config: `\n%s`", bpt, string(y))
|
||||||
|
|||||||
@@ -112,16 +112,22 @@ var generatorConfigurators = map[builtinhelpers.BuiltinPluginType]func(
|
|||||||
return
|
return
|
||||||
},
|
},
|
||||||
|
|
||||||
builtinhelpers.HelmChartInflationGenerator: func(kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f gFactory) (
|
builtinhelpers.HelmChartInflationGenerator: func(
|
||||||
|
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f gFactory) (
|
||||||
result []resmap.Generator, err error) {
|
result []resmap.Generator, err error) {
|
||||||
var c struct {
|
var c struct {
|
||||||
types.HelmChartArgs
|
types.HelmGlobals
|
||||||
|
types.HelmChart
|
||||||
}
|
}
|
||||||
for _, args := range kt.kustomization.HelmChartInflationGenerator {
|
var globals types.HelmGlobals
|
||||||
c.HelmChartArgs = args
|
if kt.kustomization.HelmGlobals != nil {
|
||||||
|
globals = *kt.kustomization.HelmGlobals
|
||||||
|
}
|
||||||
|
for _, chart := range kt.kustomization.HelmCharts {
|
||||||
|
c.HelmGlobals = globals
|
||||||
|
c.HelmChart = chart
|
||||||
p := f()
|
p := f()
|
||||||
err := kt.configureBuiltinPlugin(p, c, bpt)
|
if err = kt.configureBuiltinPlugin(p, c, bpt); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
result = append(result, p)
|
result = append(result, p)
|
||||||
@@ -201,14 +207,16 @@ var transformerConfigurators = map[builtinhelpers.BuiltinPluginType]func(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
var c struct {
|
var c struct {
|
||||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||||
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||||
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
||||||
|
Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"`
|
||||||
}
|
}
|
||||||
for _, pc := range kt.kustomization.Patches {
|
for _, pc := range kt.kustomization.Patches {
|
||||||
c.Target = pc.Target
|
c.Target = pc.Target
|
||||||
c.Patch = pc.Patch
|
c.Patch = pc.Patch
|
||||||
c.Path = pc.Path
|
c.Path = pc.Path
|
||||||
|
c.Options = pc.Options
|
||||||
p := f()
|
p := f()
|
||||||
err = kt.configureBuiltinPlugin(p, c, bpt)
|
err = kt.configureBuiltinPlugin(p, c, bpt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -221,6 +229,31 @@ var transformerConfigurators = map[builtinhelpers.BuiltinPluginType]func(
|
|||||||
builtinhelpers.LabelTransformer: func(
|
builtinhelpers.LabelTransformer: func(
|
||||||
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
|
kt *KustTarget, bpt builtinhelpers.BuiltinPluginType, f tFactory, tc *builtinconfig.TransformerConfig) (
|
||||||
result []resmap.Transformer, err error) {
|
result []resmap.Transformer, err error) {
|
||||||
|
for _, label := range kt.kustomization.Labels {
|
||||||
|
var c struct {
|
||||||
|
Labels map[string]string
|
||||||
|
FieldSpecs []types.FieldSpec
|
||||||
|
}
|
||||||
|
c.Labels = label.Pairs
|
||||||
|
fss := types.FsSlice(label.FieldSpecs)
|
||||||
|
// merge the custom fieldSpecs with the default
|
||||||
|
if label.IncludeSelectors {
|
||||||
|
fss, err = fss.MergeAll(tc.CommonLabels)
|
||||||
|
} else {
|
||||||
|
// only add to metadata by default
|
||||||
|
fss, err = fss.MergeOne(types.FieldSpec{Path: "metadata/labels", CreateIfNotPresent: true})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c.FieldSpecs = fss
|
||||||
|
p := f()
|
||||||
|
err = kt.configureBuiltinPlugin(p, c, bpt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = append(result, p)
|
||||||
|
}
|
||||||
var c struct {
|
var c struct {
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
FieldSpecs []types.FieldSpec
|
FieldSpecs []types.FieldSpec
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
pLdr "sigs.k8s.io/kustomize/api/internal/plugins/loader"
|
||||||
"sigs.k8s.io/kustomize/api/internal/target"
|
"sigs.k8s.io/kustomize/api/internal/target"
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
fLdr "sigs.k8s.io/kustomize/api/loader"
|
fLdr "sigs.k8s.io/kustomize/api/loader"
|
||||||
"sigs.k8s.io/kustomize/api/provider"
|
"sigs.k8s.io/kustomize/api/provider"
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
valtest_test "sigs.k8s.io/kustomize/api/testutils/valtest"
|
valtest_test "sigs.k8s.io/kustomize/api/testutils/valtest"
|
||||||
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeAndLoadKustTarget(
|
func makeAndLoadKustTarget(
|
||||||
@@ -36,12 +36,11 @@ func makeKustTargetWithRf(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
rf := resmap.NewFactory(
|
rf := resmap.NewFactory(pvd.GetResourceFactory())
|
||||||
pvd.GetResourceFactory(), pvd.GetConflictDetectorFactory())
|
pc := types.DisabledPluginConfig()
|
||||||
pc := konfig.DisabledPluginConfig()
|
|
||||||
return target.NewKustTarget(
|
return target.NewKustTarget(
|
||||||
ldr,
|
ldr,
|
||||||
valtest_test.MakeFakeValidator(),
|
valtest_test.MakeFakeValidator(),
|
||||||
rf,
|
rf,
|
||||||
pLdr.NewLoader(pc, rf))
|
pLdr.NewLoader(pc, rf, fSys))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,15 +4,12 @@
|
|||||||
package target
|
package target
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
"sigs.k8s.io/kustomize/api/resmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// multiTransformer contains a list of transformers.
|
// multiTransformer contains a list of transformers.
|
||||||
type multiTransformer struct {
|
type multiTransformer struct {
|
||||||
transformers []resmap.Transformer
|
transformers []resmap.Transformer
|
||||||
checkConflictEnabled bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ resmap.Transformer = &multiTransformer{}
|
var _ resmap.Transformer = &multiTransformer{}
|
||||||
@@ -20,8 +17,8 @@ var _ resmap.Transformer = &multiTransformer{}
|
|||||||
// newMultiTransformer constructs a multiTransformer.
|
// newMultiTransformer constructs a multiTransformer.
|
||||||
func newMultiTransformer(t []resmap.Transformer) resmap.Transformer {
|
func newMultiTransformer(t []resmap.Transformer) resmap.Transformer {
|
||||||
r := &multiTransformer{
|
r := &multiTransformer{
|
||||||
transformers: make([]resmap.Transformer, len(t)),
|
transformers: make([]resmap.Transformer, len(t)),
|
||||||
checkConflictEnabled: false}
|
}
|
||||||
copy(r.transformers, t)
|
copy(r.transformers, t)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@@ -29,57 +26,11 @@ func newMultiTransformer(t []resmap.Transformer) resmap.Transformer {
|
|||||||
// Transform applies the member transformers in order to the resources,
|
// Transform applies the member transformers in order to the resources,
|
||||||
// optionally detecting and erroring on commutation conflict.
|
// optionally detecting and erroring on commutation conflict.
|
||||||
func (o *multiTransformer) Transform(m resmap.ResMap) error {
|
func (o *multiTransformer) Transform(m resmap.ResMap) error {
|
||||||
if o.checkConflictEnabled {
|
|
||||||
return o.transformWithCheckConflict(m)
|
|
||||||
}
|
|
||||||
return o.transform(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *multiTransformer) transform(m resmap.ResMap) error {
|
|
||||||
for _, t := range o.transformers {
|
for _, t := range o.transformers {
|
||||||
err := t.Transform(m)
|
if err := t.Transform(m); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
m.DropEmpties()
|
||||||
for _, r := range m.Resources() {
|
|
||||||
empty, err := r.IsEmpty()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if empty {
|
|
||||||
err := m.Remove(r.CurId())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Of the len(o.transformers)! possible transformer orderings, compare to a reversed order.
|
|
||||||
// A spot check to perform when the transformations are supposed to be commutative.
|
|
||||||
// Fail if there's a difference in the result.
|
|
||||||
func (o *multiTransformer) transformWithCheckConflict(m resmap.ResMap) error {
|
|
||||||
mcopy := m.DeepCopy()
|
|
||||||
err := o.transform(m)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
o.reverseTransformers()
|
|
||||||
err = o.transform(mcopy)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = m.ErrorIfNotEqualSets(mcopy)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("found conflict between different patches\n%v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *multiTransformer) reverseTransformers() {
|
|
||||||
for i, j := 0, len(o.transformers)-1; i < j; i, j = i+1, j-1 {
|
|
||||||
o.transformers[i], o.transformers[j] = o.transformers[j], o.transformers[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package wrappy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/hasher"
|
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
|
||||||
"sigs.k8s.io/kustomize/api/internal/generators"
|
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WNodeFactory makes instances of WNode.
|
|
||||||
//
|
|
||||||
// These instances in turn adapt
|
|
||||||
// sigs.k8s.io/kustomize/kyaml/yaml.RNode
|
|
||||||
// to implement ifc.Unstructured.
|
|
||||||
// This factory is meant to implement ifc.KunstructuredFactory.
|
|
||||||
//
|
|
||||||
// This implementation should be thin, as both WNode and WNodeFactory must be
|
|
||||||
// factored away (deleted) along with ifc.Kunstructured in favor of direct use
|
|
||||||
// of RNode methods upon completion of
|
|
||||||
// https://github.com/kubernetes-sigs/kustomize/issues/2506.
|
|
||||||
//
|
|
||||||
// See also api/krusty/internal/provider/depprovider.go
|
|
||||||
type WNodeFactory struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ifc.KunstructuredFactory = (*WNodeFactory)(nil)
|
|
||||||
|
|
||||||
func (k *WNodeFactory) SliceFromBytes(bs []byte) ([]ifc.Kunstructured, error) {
|
|
||||||
yamlRNodes, err := kio.FromBytes(bs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var result []ifc.Kunstructured
|
|
||||||
for i := range yamlRNodes {
|
|
||||||
rn := yamlRNodes[i]
|
|
||||||
meta, err := rn.GetValidatedMetadata()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !shouldDropObject(meta) {
|
|
||||||
if foundNil, path := rn.HasNilEntryInList(); foundNil {
|
|
||||||
return nil, fmt.Errorf("empty item at %v in object %v", path, rn)
|
|
||||||
}
|
|
||||||
result = append(result, FromRNode(rn))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldDropObject returns true if the resource should not be accumulated.
|
|
||||||
func shouldDropObject(m yaml.ResourceMeta) bool {
|
|
||||||
_, y := m.ObjectMeta.Annotations[konfig.IgnoredByKustomizeAnnotation]
|
|
||||||
return y
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *WNodeFactory) FromMap(m map[string]interface{}) ifc.Kunstructured {
|
|
||||||
rn, err := FromMap(m)
|
|
||||||
if err != nil {
|
|
||||||
// TODO(#WNodeFactory): handle or bubble error"
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return rn
|
|
||||||
}
|
|
||||||
|
|
||||||
// kustHash computes a hash of an unstructured object.
|
|
||||||
type kustHash struct{}
|
|
||||||
|
|
||||||
// Hash returns a hash of the given object
|
|
||||||
func (h *kustHash) Hash(m ifc.Kunstructured) (string, error) {
|
|
||||||
node, err := filtersutil.GetRNode(m)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return hasher.HashRNode(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *WNodeFactory) Hasher() ifc.KunstructuredHasher {
|
|
||||||
return &kustHash{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeConfigMap makes a wrapped configmap.
|
|
||||||
func (k *WNodeFactory) MakeConfigMap(
|
|
||||||
ldr ifc.KvLoader, args *types.ConfigMapArgs) (ifc.Kunstructured, error) {
|
|
||||||
rn, err := generators.MakeConfigMap(ldr, args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return FromRNode(rn), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeSecret makes a wrapped secret.
|
|
||||||
func (k *WNodeFactory) MakeSecret(
|
|
||||||
ldr ifc.KvLoader, args *types.SecretArgs) (ifc.Kunstructured, error) {
|
|
||||||
rn, err := generators.MakeSecret(ldr, args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return FromRNode(rn), nil
|
|
||||||
}
|
|
||||||
@@ -1,324 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package wrappy_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
. "sigs.k8s.io/kustomize/api/internal/wrappy"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHasher(t *testing.T) {
|
|
||||||
input := `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: foo
|
|
||||||
data:
|
|
||||||
one: ""
|
|
||||||
binaryData:
|
|
||||||
two: ""
|
|
||||||
`
|
|
||||||
expect := "698h7c7t9m"
|
|
||||||
|
|
||||||
factory := &WNodeFactory{}
|
|
||||||
k, err := factory.SliceFromBytes([]byte(input))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hasher := factory.Hasher()
|
|
||||||
result, err := hasher.Hash(k[0])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if result != expect {
|
|
||||||
t.Fatalf("expect %s but got %s", expect, result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSliceFromBytes(t *testing.T) {
|
|
||||||
factory := &WNodeFactory{}
|
|
||||||
testConfigMap :=
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "winnie",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
testConfigMapList :=
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "ConfigMapList",
|
|
||||||
"items": []interface{}{
|
|
||||||
testConfigMap,
|
|
||||||
testConfigMap,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
testDeploymentSpec := map[string]interface{}{
|
|
||||||
"template": map[string]interface{}{
|
|
||||||
"spec": map[string]interface{}{
|
|
||||||
"hostAliases": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"hostnames": []interface{}{
|
|
||||||
"a.example.com",
|
|
||||||
},
|
|
||||||
"ip": "8.8.8.8",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
testDeploymentA := map[string]interface{}{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "deployment-a",
|
|
||||||
},
|
|
||||||
"spec": testDeploymentSpec,
|
|
||||||
}
|
|
||||||
testDeploymentB := map[string]interface{}{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "deployment-b",
|
|
||||||
},
|
|
||||||
"spec": testDeploymentSpec,
|
|
||||||
}
|
|
||||||
testDeploymentList :=
|
|
||||||
map[string]interface{}{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"kind": "DeploymentList",
|
|
||||||
"items": []interface{}{
|
|
||||||
testDeploymentA,
|
|
||||||
testDeploymentB,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type expected struct {
|
|
||||||
out []map[string]interface{}
|
|
||||||
isErr bool
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input []byte
|
|
||||||
exp expected
|
|
||||||
}{
|
|
||||||
"garbage": {
|
|
||||||
input: []byte("garbageIn: garbageOut"),
|
|
||||||
exp: expected{
|
|
||||||
isErr: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"noBytes": {
|
|
||||||
input: []byte{},
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"goodJson": {
|
|
||||||
input: []byte(`
|
|
||||||
{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{testConfigMap},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"goodYaml1": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{testConfigMap},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"goodYaml2": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{testConfigMap, testConfigMap},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"localConfigYaml": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie-skip
|
|
||||||
annotations:
|
|
||||||
# this annotation causes the Resource to be ignored by kustomize
|
|
||||||
config.kubernetes.io/local-config: ""
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{testConfigMap},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"garbageInOneOfTwoObjects": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
---
|
|
||||||
WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
isErr: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"emptyObjects": {
|
|
||||||
input: []byte(`
|
|
||||||
---
|
|
||||||
#a comment
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Missing .metadata.name in object": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
foo: bar
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
isErr: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"nil value in list": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: builtin
|
|
||||||
kind: ConfigMapGenerator
|
|
||||||
metadata:
|
|
||||||
name: kube100-site
|
|
||||||
labels:
|
|
||||||
app: web
|
|
||||||
testList:
|
|
||||||
- testA
|
|
||||||
-
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
isErr: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"List": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: List
|
|
||||||
items:
|
|
||||||
- apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
- apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{
|
|
||||||
testConfigMap,
|
|
||||||
testConfigMap},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"ConfigMapList": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMapList
|
|
||||||
items:
|
|
||||||
- apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
- apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
metadata:
|
|
||||||
name: winnie
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{testConfigMapList},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"listWithAnchors": {
|
|
||||||
input: []byte(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: DeploymentList
|
|
||||||
items:
|
|
||||||
- apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deployment-a
|
|
||||||
spec: &hostAliases
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
hostAliases:
|
|
||||||
- hostnames:
|
|
||||||
- a.example.com
|
|
||||||
ip: 8.8.8.8
|
|
||||||
- apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deployment-b
|
|
||||||
spec:
|
|
||||||
<<: *hostAliases
|
|
||||||
`),
|
|
||||||
exp: expected{
|
|
||||||
out: []map[string]interface{}{testDeploymentList},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for n := range testCases {
|
|
||||||
tc := testCases[n]
|
|
||||||
t.Run(n, func(t *testing.T) {
|
|
||||||
rs, err := factory.SliceFromBytes(tc.input)
|
|
||||||
if err != nil {
|
|
||||||
assert.True(t, tc.exp.isErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
assert.False(t, tc.exp.isErr)
|
|
||||||
assert.Equal(t, len(tc.exp.out), len(rs))
|
|
||||||
for i := range rs {
|
|
||||||
rsMap, err := rs[i].Map()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(
|
|
||||||
t, fmt.Sprintf("%v", tc.exp.out[i]), fmt.Sprintf("%v", rsMap))
|
|
||||||
if n != "listWithAnchors" {
|
|
||||||
// https://github.com/kubernetes-sigs/kustomize/issues/3271
|
|
||||||
m, _ := rs[i].Map()
|
|
||||||
if !reflect.DeepEqual(tc.exp.out[i], m) {
|
|
||||||
t.Fatalf("%s:\nexpected: %v\n actual: %v",
|
|
||||||
n, tc.exp.out[i], m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package wrappy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// WNode implements ifc.Kunstructured using yaml.RNode.
|
|
||||||
//
|
|
||||||
// It exists only to help manage a switch from
|
|
||||||
// kunstruct.UnstructAdapter to yaml.RNode as the core
|
|
||||||
// representation of KRM objects in kustomize.
|
|
||||||
//
|
|
||||||
// It's got a silly name because we don't want it around for long,
|
|
||||||
// and want its use to be obvious.
|
|
||||||
type WNode struct {
|
|
||||||
node *yaml.RNode
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ifc.Kunstructured = (*WNode)(nil)
|
|
||||||
|
|
||||||
func NewWNode() *WNode {
|
|
||||||
return FromRNode(yaml.NewRNode(nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromMap(m map[string]interface{}) (*WNode, error) {
|
|
||||||
n, err := yaml.FromMap(m)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return FromRNode(n), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromRNode(node *yaml.RNode) *WNode {
|
|
||||||
return &WNode{node: node}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wn *WNode) AsRNode() *yaml.RNode {
|
|
||||||
return wn.node
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wn *WNode) demandMetaData(label string) yaml.ResourceMeta {
|
|
||||||
meta, err := wn.node.GetMeta()
|
|
||||||
if err != nil {
|
|
||||||
// Log and die since interface doesn't allow error.
|
|
||||||
log.Fatalf("for %s', expected valid resource: %v", label, err)
|
|
||||||
}
|
|
||||||
return meta
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) Copy() ifc.Kunstructured {
|
|
||||||
return &WNode{node: wn.node.Copy()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAnnotations implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetAnnotations() map[string]string {
|
|
||||||
return wn.demandMetaData("GetAnnotations").Annotations
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertSliceIndex traverses the items in `fields` and find
|
|
||||||
// if there is a slice index in the item and change it to a
|
|
||||||
// valid Lookup field path. For example, 'ports[0]' will be
|
|
||||||
// converted to 'ports' and '0'.
|
|
||||||
func convertSliceIndex(fields []string) []string {
|
|
||||||
var res []string
|
|
||||||
for _, s := range fields {
|
|
||||||
if !strings.HasSuffix(s, "]") {
|
|
||||||
res = append(res, s)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
re := regexp.MustCompile(`^(.*)\[(\d+)\]$`)
|
|
||||||
groups := re.FindStringSubmatch(s)
|
|
||||||
if len(groups) == 0 {
|
|
||||||
// no match, add to result
|
|
||||||
res = append(res, s)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if groups[1] != "" {
|
|
||||||
res = append(res, groups[1])
|
|
||||||
}
|
|
||||||
res = append(res, groups[2])
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFieldValue implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetFieldValue(path string) (interface{}, error) {
|
|
||||||
fields := convertSliceIndex(strings.Split(path, "."))
|
|
||||||
rn, err := wn.node.Pipe(yaml.Lookup(fields...))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if rn == nil {
|
|
||||||
return nil, NoFieldError{path}
|
|
||||||
}
|
|
||||||
yn := rn.YNode()
|
|
||||||
|
|
||||||
// If this is an alias node, resolve it
|
|
||||||
if yn.Kind == yaml.AliasNode {
|
|
||||||
yn = yn.Alias
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return value as map for DocumentNode and MappingNode kinds
|
|
||||||
if yn.Kind == yaml.DocumentNode || yn.Kind == yaml.MappingNode {
|
|
||||||
var result map[string]interface{}
|
|
||||||
if err := yn.Decode(&result); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return value as slice for SequenceNode kind
|
|
||||||
if yn.Kind == yaml.SequenceNode {
|
|
||||||
var result []interface{}
|
|
||||||
if err := yn.Decode(&result); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
if yn.Kind != yaml.ScalarNode {
|
|
||||||
return nil, fmt.Errorf("expected ScalarNode, got Kind=%d", yn.Kind)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: When doing kustomize var replacement, which is likely a
|
|
||||||
// a primary use of this function and the reason it returns interface{}
|
|
||||||
// rather than string, we do conversion from Nodes to Go types and back
|
|
||||||
// to nodes. We should figure out how to do replacement using raw nodes,
|
|
||||||
// assuming we keep the var feature in kustomize.
|
|
||||||
// The other end of this is: refvar.go:updateNodeValue.
|
|
||||||
switch yn.Tag {
|
|
||||||
case yaml.NodeTagString:
|
|
||||||
return yn.Value, nil
|
|
||||||
case yaml.NodeTagInt:
|
|
||||||
return strconv.Atoi(yn.Value)
|
|
||||||
case yaml.NodeTagFloat:
|
|
||||||
return strconv.ParseFloat(yn.Value, 64)
|
|
||||||
case yaml.NodeTagBool:
|
|
||||||
return strconv.ParseBool(yn.Value)
|
|
||||||
default:
|
|
||||||
// Possibly this should be an error or log.
|
|
||||||
return yn.Value, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGvk implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetGvk() resid.Gvk {
|
|
||||||
meta := wn.demandMetaData("GetGvk")
|
|
||||||
g, v := resid.ParseGroupVersion(meta.APIVersion)
|
|
||||||
return resid.Gvk{Group: g, Version: v, Kind: meta.Kind}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDataMap implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetDataMap() map[string]string {
|
|
||||||
return wn.node.GetDataMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDataMap implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetDataMap(m map[string]string) {
|
|
||||||
wn.node.SetDataMap(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBinaryDataMap implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetBinaryDataMap() map[string]string {
|
|
||||||
return wn.node.GetBinaryDataMap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetBinaryDataMap implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetBinaryDataMap(m map[string]string) {
|
|
||||||
wn.node.SetBinaryDataMap(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetKind implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetKind() string {
|
|
||||||
return wn.demandMetaData("GetKind").Kind
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLabels implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetLabels() map[string]string {
|
|
||||||
return wn.demandMetaData("GetLabels").Labels
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetName() string {
|
|
||||||
return wn.demandMetaData("GetName").Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSlice implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetSlice(path string) ([]interface{}, error) {
|
|
||||||
value, err := wn.GetFieldValue(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if sliceValue, ok := value.([]interface{}); ok {
|
|
||||||
return sliceValue, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("node %s is not a slice", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSlice implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) GetString(path string) (string, error) {
|
|
||||||
value, err := wn.GetFieldValue(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if v, ok := value.(string); ok {
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("node %s is not a string: %v", path, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) Map() (map[string]interface{}, error) {
|
|
||||||
return wn.node.Map()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) MarshalJSON() ([]byte, error) {
|
|
||||||
return wn.node.MarshalJSON()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchesAnnotationSelector implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) MatchesAnnotationSelector(selector string) (bool, error) {
|
|
||||||
return wn.node.MatchesAnnotationSelector(selector)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchesLabelSelector implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) MatchesLabelSelector(selector string) (bool, error) {
|
|
||||||
return wn.node.MatchesLabelSelector(selector)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAnnotations implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetAnnotations(annotations map[string]string) {
|
|
||||||
if err := wn.node.SetAnnotations(annotations); err != nil {
|
|
||||||
log.Fatal(err) // interface doesn't allow error.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetGvk implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetGvk(gvk resid.Gvk) {
|
|
||||||
wn.setMapField(yaml.NewScalarRNode(gvk.Kind), yaml.KindField)
|
|
||||||
wn.setMapField(yaml.NewScalarRNode(gvk.ApiVersion()), yaml.APIVersionField)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetLabels implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetLabels(labels map[string]string) {
|
|
||||||
if err := wn.node.SetLabels(labels); err != nil {
|
|
||||||
log.Fatal(err) // interface doesn't allow error.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetName implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetName(name string) {
|
|
||||||
wn.setMapField(yaml.NewScalarRNode(name), yaml.MetadataField, yaml.NameField)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetNamespace implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) SetNamespace(ns string) {
|
|
||||||
if err := wn.node.SetNamespace(ns); err != nil {
|
|
||||||
log.Fatal(err) // interface doesn't allow error.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wn *WNode) setMapField(value *yaml.RNode, path ...string) {
|
|
||||||
if err := wn.node.SetMapField(value, path...); err != nil {
|
|
||||||
// Log and die since interface doesn't allow error.
|
|
||||||
log.Fatalf("failed to set field %v: %v", path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements ifc.Kunstructured.
|
|
||||||
func (wn *WNode) UnmarshalJSON(data []byte) error {
|
|
||||||
return wn.node.UnmarshalJSON(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
type NoFieldError struct {
|
|
||||||
Field string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e NoFieldError) Error() string {
|
|
||||||
return fmt.Sprintf("no field named '%s'", e.Field)
|
|
||||||
}
|
|
||||||
@@ -1,670 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package wrappy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
|
||||||
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
deploymentLittleJson = `{"apiVersion":"apps/v1","kind":"Deployment",` +
|
|
||||||
`"metadata":{"name":"homer","namespace":"simpsons"}}`
|
|
||||||
|
|
||||||
deploymentBiggerJson = `
|
|
||||||
{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": {
|
|
||||||
"name": "homer",
|
|
||||||
"namespace": "simpsons",
|
|
||||||
"labels": {
|
|
||||||
"fruit": "apple",
|
|
||||||
"veggie": "carrot"
|
|
||||||
},
|
|
||||||
"annotations": {
|
|
||||||
"area": "51",
|
|
||||||
"greeting": "Take me to your leader."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"template": {
|
|
||||||
"spec": {
|
|
||||||
"containers": [
|
|
||||||
{
|
|
||||||
"env": [
|
|
||||||
{
|
|
||||||
"name": "CM_FOO",
|
|
||||||
"valueFrom": {
|
|
||||||
"configMapKeyRef": {
|
|
||||||
"key": "somekey",
|
|
||||||
"name": "myCm"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "SECRET_FOO",
|
|
||||||
"valueFrom": {
|
|
||||||
"secretKeyRef": {
|
|
||||||
"key": "someKey",
|
|
||||||
"name": "mySecret"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"image": "nginx:1.7.9",
|
|
||||||
"name": "nginx"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`
|
|
||||||
bigMapYaml = `Kind: Service
|
|
||||||
complextree:
|
|
||||||
- field1:
|
|
||||||
- boolfield: true
|
|
||||||
floatsubfield: 1.01
|
|
||||||
intsubfield: 1010
|
|
||||||
stringsubfield: idx1010
|
|
||||||
- boolfield: false
|
|
||||||
floatsubfield: 1.011
|
|
||||||
intsubfield: 1011
|
|
||||||
stringsubfield: idx1011
|
|
||||||
field2:
|
|
||||||
- boolfield: true
|
|
||||||
floatsubfield: 1.02
|
|
||||||
intsubfield: 1020
|
|
||||||
stringsubfield: idx1020
|
|
||||||
- boolfield: false
|
|
||||||
floatsubfield: 1.021
|
|
||||||
intsubfield: 1021
|
|
||||||
stringsubfield: idx1021
|
|
||||||
- field1:
|
|
||||||
- boolfield: true
|
|
||||||
floatsubfield: 1.11
|
|
||||||
intsubfield: 1110
|
|
||||||
stringsubfield: idx1110
|
|
||||||
- boolfield: false
|
|
||||||
floatsubfield: 1.111
|
|
||||||
intsubfield: 1111
|
|
||||||
stringsubfield: idx1111
|
|
||||||
field2:
|
|
||||||
- boolfield: true
|
|
||||||
floatsubfield: 1.112
|
|
||||||
intsubfield: 1120
|
|
||||||
stringsubfield: idx1120
|
|
||||||
- boolfield: false
|
|
||||||
floatsubfield: 1.1121
|
|
||||||
intsubfield: 1121
|
|
||||||
stringsubfield: idx1121
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: application-name
|
|
||||||
name: service-name
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
port: 80
|
|
||||||
that:
|
|
||||||
- idx0
|
|
||||||
- idx1
|
|
||||||
- idx2
|
|
||||||
- idx3
|
|
||||||
these:
|
|
||||||
- field1:
|
|
||||||
- idx010
|
|
||||||
- idx011
|
|
||||||
field2:
|
|
||||||
- idx020
|
|
||||||
- idx021
|
|
||||||
- field1:
|
|
||||||
- idx110
|
|
||||||
- idx111
|
|
||||||
field2:
|
|
||||||
- idx120
|
|
||||||
- idx121
|
|
||||||
- field1:
|
|
||||||
- idx210
|
|
||||||
- idx211
|
|
||||||
field2:
|
|
||||||
- idx220
|
|
||||||
- idx221
|
|
||||||
this:
|
|
||||||
is:
|
|
||||||
aBool: true
|
|
||||||
aFloat: 1.001
|
|
||||||
aNilValue: null
|
|
||||||
aNumber: 1000
|
|
||||||
anEmptyMap: {}
|
|
||||||
anEmptySlice: []
|
|
||||||
those:
|
|
||||||
- field1: idx0foo
|
|
||||||
field2: idx0bar
|
|
||||||
- field1: idx1foo
|
|
||||||
field2: idx1bar
|
|
||||||
- field1: idx2foo
|
|
||||||
field2: idx2bar
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeBigMap() map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"Kind": "Service",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"labels": map[string]interface{}{
|
|
||||||
"app": "application-name",
|
|
||||||
},
|
|
||||||
"name": "service-name",
|
|
||||||
},
|
|
||||||
"spec": map[string]interface{}{
|
|
||||||
"ports": map[string]interface{}{
|
|
||||||
"port": int64(80),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"this": map[string]interface{}{
|
|
||||||
"is": map[string]interface{}{
|
|
||||||
"aNumber": int64(1000),
|
|
||||||
"aFloat": float64(1.001),
|
|
||||||
"aNilValue": nil,
|
|
||||||
"aBool": true,
|
|
||||||
"anEmptyMap": map[string]interface{}{},
|
|
||||||
"anEmptySlice": []interface{}{},
|
|
||||||
/*
|
|
||||||
TODO: test for unrecognizable (e.g. a function)
|
|
||||||
"unrecognizable": testing.InternalExample{
|
|
||||||
Name: "fooBar",
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"that": []interface{}{
|
|
||||||
"idx0",
|
|
||||||
"idx1",
|
|
||||||
"idx2",
|
|
||||||
"idx3",
|
|
||||||
},
|
|
||||||
"those": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": "idx0foo",
|
|
||||||
"field2": "idx0bar",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": "idx1foo",
|
|
||||||
"field2": "idx1bar",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": "idx2foo",
|
|
||||||
"field2": "idx2bar",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"these": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": []interface{}{"idx010", "idx011"},
|
|
||||||
"field2": []interface{}{"idx020", "idx021"},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": []interface{}{"idx110", "idx111"},
|
|
||||||
"field2": []interface{}{"idx120", "idx121"},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": []interface{}{"idx210", "idx211"},
|
|
||||||
"field2": []interface{}{"idx220", "idx221"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"complextree": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1010",
|
|
||||||
"intsubfield": int64(1010),
|
|
||||||
"floatsubfield": float64(1.010),
|
|
||||||
"boolfield": true,
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1011",
|
|
||||||
"intsubfield": int64(1011),
|
|
||||||
"floatsubfield": float64(1.011),
|
|
||||||
"boolfield": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"field2": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1020",
|
|
||||||
"intsubfield": int64(1020),
|
|
||||||
"floatsubfield": float64(1.020),
|
|
||||||
"boolfield": true,
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1021",
|
|
||||||
"intsubfield": int64(1021),
|
|
||||||
"floatsubfield": float64(1.021),
|
|
||||||
"boolfield": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1110",
|
|
||||||
"intsubfield": int64(1110),
|
|
||||||
"floatsubfield": float64(1.110),
|
|
||||||
"boolfield": true,
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1111",
|
|
||||||
"intsubfield": int64(1111),
|
|
||||||
"floatsubfield": float64(1.111),
|
|
||||||
"boolfield": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"field2": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1120",
|
|
||||||
"intsubfield": int64(1120),
|
|
||||||
"floatsubfield": float64(1.1120),
|
|
||||||
"boolfield": true,
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"stringsubfield": "idx1121",
|
|
||||||
"intsubfield": int64(1121),
|
|
||||||
"floatsubfield": float64(1.1121),
|
|
||||||
"boolfield": false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBasicYamlOperationFromMap(t *testing.T) {
|
|
||||||
bytes, err := yaml.Marshal(makeBigMap())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
if string(bytes) != bigMapYaml {
|
|
||||||
t.Fatalf("unexpected string equality")
|
|
||||||
}
|
|
||||||
rNode, err := kyaml.Parse(string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
rNodeString := rNode.MustString()
|
|
||||||
// The result from MustString has more indentation
|
|
||||||
// than bigMapYaml.
|
|
||||||
rNodeStrings := strings.Split(rNodeString, "\n")
|
|
||||||
bigMapStrings := strings.Split(bigMapYaml, "\n")
|
|
||||||
if len(rNodeStrings) != len(bigMapStrings) {
|
|
||||||
t.Fatalf("line count mismatch")
|
|
||||||
}
|
|
||||||
for i := range rNodeStrings {
|
|
||||||
s1 := strings.TrimSpace(rNodeStrings[i])
|
|
||||||
s2 := strings.TrimSpace(bigMapStrings[i])
|
|
||||||
if s1 != s2 {
|
|
||||||
t.Fatalf("expected '%s'=='%s'", s1, s2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoundTripJSON(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
err := wn.UnmarshalJSON([]byte(deploymentLittleJson))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected UnmarshalJSON err: %v", err)
|
|
||||||
}
|
|
||||||
data, err := wn.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected MarshalJSON err: %v", err)
|
|
||||||
}
|
|
||||||
actual := string(data)
|
|
||||||
if actual != deploymentLittleJson {
|
|
||||||
t.Fatalf("expected %s, got %s", deploymentLittleJson, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGettingFields(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
err := wn.UnmarshalJSON([]byte(deploymentBiggerJson))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
gvk := wn.GetGvk()
|
|
||||||
expected := "apps"
|
|
||||||
actual := gvk.Group
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
expected = "v1"
|
|
||||||
actual = gvk.Version
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
expected = "Deployment"
|
|
||||||
actual = gvk.Kind
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
actual = wn.GetKind()
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
expected = "homer"
|
|
||||||
actual = wn.GetName()
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
actualMap := wn.GetLabels()
|
|
||||||
v, ok := actualMap["fruit"]
|
|
||||||
if !ok || v != "apple" {
|
|
||||||
t.Fatalf("unexpected labels '%v'", actualMap)
|
|
||||||
}
|
|
||||||
actualMap = wn.GetAnnotations()
|
|
||||||
v, ok = actualMap["greeting"]
|
|
||||||
if !ok || v != "Take me to your leader." {
|
|
||||||
t.Fatalf("unexpected annotations '%v'", actualMap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFieldValueReturnsMap(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
expected := map[string]interface{}{
|
|
||||||
"fruit": "apple",
|
|
||||||
"veggie": "carrot",
|
|
||||||
}
|
|
||||||
actual, err := wn.GetFieldValue("metadata.labels")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting field value: %v", err)
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
||||||
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFieldValueReturnsStuff(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
expected := []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"env": []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "CM_FOO",
|
|
||||||
"valueFrom": map[string]interface{}{
|
|
||||||
"configMapKeyRef": map[string]interface{}{
|
|
||||||
"key": "somekey",
|
|
||||||
"name": "myCm",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"name": "SECRET_FOO",
|
|
||||||
"valueFrom": map[string]interface{}{
|
|
||||||
"secretKeyRef": map[string]interface{}{
|
|
||||||
"key": "someKey",
|
|
||||||
"name": "mySecret",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"image": string("nginx:1.7.9"),
|
|
||||||
"name": string("nginx"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
actual, err := wn.GetFieldValue("spec.template.spec.containers")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting field value: %v", err)
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
||||||
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
|
|
||||||
}
|
|
||||||
// Cannot go deeper yet.
|
|
||||||
_, err = wn.GetFieldValue("spec.template.spec.containers.env")
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected err %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFieldValueReturnsSlice(t *testing.T) {
|
|
||||||
bytes, err := yaml.Marshal(makeBigMap())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
rNode, err := kyaml.Parse(string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
wn := FromRNode(rNode)
|
|
||||||
expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
|
|
||||||
actual, err := wn.GetFieldValue("that")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting slice: %v", err)
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
||||||
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFieldValueReturnsSliceOfMappings(t *testing.T) {
|
|
||||||
bytes, err := yaml.Marshal(makeBigMap())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
rNode, err := kyaml.Parse(string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
wn := FromRNode(rNode)
|
|
||||||
expected := []interface{}{
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": "idx0foo",
|
|
||||||
"field2": "idx0bar",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": "idx1foo",
|
|
||||||
"field2": "idx1bar",
|
|
||||||
},
|
|
||||||
map[string]interface{}{
|
|
||||||
"field1": "idx2foo",
|
|
||||||
"field2": "idx2bar",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
actual, err := wn.GetFieldValue("those")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting slice: %v", err)
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
||||||
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFieldValueReturnsString(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
actual, err := wn.GetFieldValue("metadata.labels.fruit")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting field value: %v", err)
|
|
||||||
}
|
|
||||||
v, ok := actual.(string)
|
|
||||||
if !ok || v != "apple" {
|
|
||||||
t.Fatalf("unexpected value '%v'", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetFieldValueResolvesAlias(t *testing.T) {
|
|
||||||
yamlWithAlias := `
|
|
||||||
foo: &a theValue
|
|
||||||
bar: *a
|
|
||||||
`
|
|
||||||
rNode, err := kyaml.Parse(yamlWithAlias)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml parse error: %v", err)
|
|
||||||
}
|
|
||||||
wn := FromRNode(rNode)
|
|
||||||
actual, err := wn.GetFieldValue("bar")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting field value: %v", err)
|
|
||||||
}
|
|
||||||
v, ok := actual.(string)
|
|
||||||
if !ok || v != "theValue" {
|
|
||||||
t.Fatalf("unexpected value '%v'", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetString(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
expected := "carrot"
|
|
||||||
actual, err := wn.GetString("metadata.labels.veggie")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting string: %v", err)
|
|
||||||
}
|
|
||||||
if expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetSlice(t *testing.T) {
|
|
||||||
bytes, err := yaml.Marshal(makeBigMap())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
rNode, err := kyaml.Parse(string(bytes))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected yaml.Marshal err: %v", err)
|
|
||||||
}
|
|
||||||
wn := FromRNode(rNode)
|
|
||||||
expected := []interface{}{"idx0", "idx1", "idx2", "idx3"}
|
|
||||||
actual, err := wn.GetSlice("that")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("error getting slice: %v", err)
|
|
||||||
}
|
|
||||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
||||||
t.Fatalf("actual slice does not deep equal expected slice:\n%v", diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMapEmpty(t *testing.T) {
|
|
||||||
newNodeMap, err := NewWNode().Map()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, 0, len(newNodeMap))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMap(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentLittleJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := map[string]interface{}{
|
|
||||||
"apiVersion": "apps/v1",
|
|
||||||
"kind": "Deployment",
|
|
||||||
"metadata": map[string]interface{}{
|
|
||||||
"name": "homer",
|
|
||||||
"namespace": "simpsons",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
actual, err := wn.Map()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
if diff := cmp.Diff(expected, actual); diff != "" {
|
|
||||||
t.Fatalf("actual map does not deep equal expected map:\n%v", diff)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetName(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
wn.SetName("marge")
|
|
||||||
if expected, actual := "marge", wn.GetName(); expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetNamespace(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
wn.SetNamespace("flanders")
|
|
||||||
meta, _ := wn.node.GetMeta()
|
|
||||||
if expected, actual := "flanders", meta.Namespace; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetLabels(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
wn.SetLabels(map[string]string{
|
|
||||||
"label1": "foo",
|
|
||||||
"label2": "bar",
|
|
||||||
})
|
|
||||||
labels := wn.GetLabels()
|
|
||||||
if expected, actual := 2, len(labels); expected != actual {
|
|
||||||
t.Fatalf("expected '%d', got '%d'", expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := "foo", labels["label1"]; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := "bar", labels["label2"]; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetAnnotations(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
wn.SetAnnotations(map[string]string{
|
|
||||||
"annotation1": "foo",
|
|
||||||
"annotation2": "bar",
|
|
||||||
})
|
|
||||||
annotations := wn.GetAnnotations()
|
|
||||||
if expected, actual := 2, len(annotations); expected != actual {
|
|
||||||
t.Fatalf("expected '%d', got '%d'", expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := "foo", annotations["annotation1"]; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := "bar", annotations["annotation2"]; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetGvk(t *testing.T) {
|
|
||||||
wn := NewWNode()
|
|
||||||
if err := wn.UnmarshalJSON([]byte(deploymentBiggerJson)); err != nil {
|
|
||||||
t.Fatalf("unexpected unmarshaljson err: %v", err)
|
|
||||||
}
|
|
||||||
wn.SetGvk(resid.GvkFromString("grp_ver_knd"))
|
|
||||||
gvk := wn.GetGvk()
|
|
||||||
if expected, actual := "grp", gvk.Group; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := "ver", gvk.Version; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
if expected, actual := "knd", gvk.Kind; expected != actual {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,5 +19,8 @@ namespace:
|
|||||||
group: apiregistration.k8s.io
|
group: apiregistration.k8s.io
|
||||||
kind: APIService
|
kind: APIService
|
||||||
create: true
|
create: true
|
||||||
|
- path: spec/conversion/webhook/clientConfig/service/namespace
|
||||||
|
group: apiextensions.k8s.io
|
||||||
|
kind: CustomResourceDefinition
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,31 +19,7 @@ func DefaultKustomizationFileName() string {
|
|||||||
return RecognizedKustomizationFileNames()[0]
|
return RecognizedKustomizationFileNames()[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// IfApiMachineryElseKyaml returns true if executing the apimachinery code
|
|
||||||
// path, else we're executing the kyaml code paths.
|
|
||||||
func IfApiMachineryElseKyaml(s1, s2 string) string {
|
|
||||||
if !FlagEnableKyamlDefaultValue {
|
|
||||||
return s1
|
|
||||||
}
|
|
||||||
return s2
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// FlagEnableKyamlDefaultValue is the default value for the --enable_kyaml
|
|
||||||
// flag. This value is also used in unit tests. See provider.DepProvider.
|
|
||||||
//
|
|
||||||
// TODO(#3588): Delete this constant.
|
|
||||||
//
|
|
||||||
// All tests should pass for either true or false values
|
|
||||||
// of this constant, without having to check its value.
|
|
||||||
// In the cases where there's a different outcome, either decide
|
|
||||||
// that the difference is acceptable, or make the difference go away.
|
|
||||||
//
|
|
||||||
// Historically, tests passed for enable_kyaml == false, i.e. using
|
|
||||||
// apimachinery libs. This doesn't mean the code was better, it just
|
|
||||||
// means regression tests preserved those outcomes.
|
|
||||||
FlagEnableKyamlDefaultValue = true
|
|
||||||
|
|
||||||
// An environment variable to consult for kustomization
|
// An environment variable to consult for kustomization
|
||||||
// configuration data. See:
|
// configuration data. See:
|
||||||
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||||
|
|||||||
@@ -41,32 +41,6 @@ const (
|
|||||||
NoPluginHomeSentinal = "/No/non-builtin/plugins!"
|
NoPluginHomeSentinal = "/No/non-builtin/plugins!"
|
||||||
)
|
)
|
||||||
|
|
||||||
func EnabledPluginConfig(b types.BuiltinPluginLoadingOptions) (*types.PluginConfig, error) {
|
|
||||||
dir, err := DefaultAbsPluginHome(filesys.MakeFsOnDisk())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return MakePluginConfig(types.PluginRestrictionsNone, b, dir), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DisabledPluginConfig() *types.PluginConfig {
|
|
||||||
return MakePluginConfig(
|
|
||||||
types.PluginRestrictionsBuiltinsOnly,
|
|
||||||
types.BploUseStaticallyLinked,
|
|
||||||
NoPluginHomeSentinal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakePluginConfig(
|
|
||||||
pr types.PluginRestrictions,
|
|
||||||
b types.BuiltinPluginLoadingOptions,
|
|
||||||
home string) *types.PluginConfig {
|
|
||||||
return &types.PluginConfig{
|
|
||||||
PluginRestrictions: pr,
|
|
||||||
AbsPluginHome: home,
|
|
||||||
BpLoadingOptions: b,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type NotedFunc struct {
|
type NotedFunc struct {
|
||||||
Note string
|
Note string
|
||||||
F func() string
|
F func() string
|
||||||
@@ -77,7 +51,7 @@ type NotedFunc struct {
|
|||||||
// the home of kustomize plugins.
|
// the home of kustomize plugins.
|
||||||
func DefaultAbsPluginHome(fSys filesys.FileSystem) (string, error) {
|
func DefaultAbsPluginHome(fSys filesys.FileSystem) (string, error) {
|
||||||
return FirstDirThatExistsElseError(
|
return FirstDirThatExistsElseError(
|
||||||
"plugin home directory", fSys, []NotedFunc{
|
"plugin root", fSys, []NotedFunc{
|
||||||
{
|
{
|
||||||
Note: "homed in $" + KustomizePluginHomeEnv,
|
Note: "homed in $" + KustomizePluginHomeEnv,
|
||||||
F: func() string {
|
F: func() string {
|
||||||
@@ -87,9 +61,11 @@ func DefaultAbsPluginHome(fSys filesys.FileSystem) (string, error) {
|
|||||||
{
|
{
|
||||||
Note: "homed in $" + XdgConfigHomeEnv,
|
Note: "homed in $" + XdgConfigHomeEnv,
|
||||||
F: func() string {
|
F: func() string {
|
||||||
return filepath.Join(
|
if root := os.Getenv(XdgConfigHomeEnv); root != "" {
|
||||||
os.Getenv(XdgConfigHomeEnv),
|
return filepath.Join(root, ProgramName, RelPluginHome)
|
||||||
ProgramName, RelPluginHome)
|
}
|
||||||
|
// do not look in "kustomize/plugin" if XdgConfigHomeEnv is unset
|
||||||
|
return ""
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -118,11 +94,14 @@ func FirstDirThatExistsElseError(
|
|||||||
pathFuncs []NotedFunc) (string, error) {
|
pathFuncs []NotedFunc) (string, error) {
|
||||||
var nope []types.Pair
|
var nope []types.Pair
|
||||||
for _, dt := range pathFuncs {
|
for _, dt := range pathFuncs {
|
||||||
dir := dt.F()
|
if dir := dt.F(); dir != "" {
|
||||||
if fSys.Exists(dir) {
|
if fSys.Exists(dir) {
|
||||||
return dir, nil
|
return dir, nil
|
||||||
|
}
|
||||||
|
nope = append(nope, types.Pair{Key: dt.Note, Value: dir})
|
||||||
|
} else {
|
||||||
|
nope = append(nope, types.Pair{Key: dt.Note, Value: "<no value>"})
|
||||||
}
|
}
|
||||||
nope = append(nope, types.Pair{Key: dt.Note, Value: dir})
|
|
||||||
}
|
}
|
||||||
return "", types.NewErrUnableToFind(what, nope)
|
return "", types.NewErrUnableToFind(what, nope)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
)
|
)
|
||||||
@@ -28,6 +30,34 @@ func TestDefaultAbsPluginHome_NoKustomizePluginHomeEnv(t *testing.T) {
|
|||||||
if !types.IsErrUnableToFind(err) {
|
if !types.IsErrUnableToFind(err) {
|
||||||
t.Fatalf("unexpected err: %v", err)
|
t.Fatalf("unexpected err: %v", err)
|
||||||
}
|
}
|
||||||
|
for _, expectedMsg := range []string{
|
||||||
|
"unable to find plugin root - tried:",
|
||||||
|
"('<no value>'; homed in $KUSTOMIZE_PLUGIN_HOME)",
|
||||||
|
"; homed in $XDG_CONFIG_HOME)",
|
||||||
|
"/.config/kustomize/plugin'; homed in default value of $XDG_CONFIG_HOME)",
|
||||||
|
"/kustomize/plugin'; homed in home directory)",
|
||||||
|
} {
|
||||||
|
assert.Contains(t, err.Error(), expectedMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultAbsPluginHome_EmptyKustomizePluginHomeEnv(t *testing.T) {
|
||||||
|
keep, isSet := os.LookupEnv(KustomizePluginHomeEnv)
|
||||||
|
os.Setenv(KustomizePluginHomeEnv, "")
|
||||||
|
|
||||||
|
_, err := DefaultAbsPluginHome(filesys.MakeFsInMemory())
|
||||||
|
if !isSet {
|
||||||
|
_ = os.Unsetenv(KustomizePluginHomeEnv)
|
||||||
|
} else {
|
||||||
|
_ = os.Setenv(KustomizePluginHomeEnv, keep)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected err")
|
||||||
|
}
|
||||||
|
if !types.IsErrUnableToFind(err) {
|
||||||
|
t.Fatalf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
assert.Contains(t, err.Error(), "('<no value>'; homed in $KUSTOMIZE_PLUGIN_HOME)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultAbsPluginHome_WithKustomizePluginHomeEnv(t *testing.T) {
|
func TestDefaultAbsPluginHome_WithKustomizePluginHomeEnv(t *testing.T) {
|
||||||
@@ -89,6 +119,25 @@ func TestDefaultAbsPluginHomeNoConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDefaultAbsPluginHomeEmptyXdgConfig(t *testing.T) {
|
||||||
|
keep, isSet := os.LookupEnv(XdgConfigHomeEnv)
|
||||||
|
os.Setenv(XdgConfigHomeEnv, "")
|
||||||
|
if isSet {
|
||||||
|
_ = os.Unsetenv(XdgConfigHomeEnv)
|
||||||
|
}
|
||||||
|
_, err := DefaultAbsPluginHome(filesys.MakeFsInMemory())
|
||||||
|
if isSet {
|
||||||
|
os.Setenv(XdgConfigHomeEnv, keep)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected err")
|
||||||
|
}
|
||||||
|
if !types.IsErrUnableToFind(err) {
|
||||||
|
t.Fatalf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
assert.Contains(t, err.Error(), "('<no value>'; homed in $XDG_CONFIG_HOME)")
|
||||||
|
}
|
||||||
|
|
||||||
func TestDefaultAbsPluginHomeNoXdgWithDotConfig(t *testing.T) {
|
func TestDefaultAbsPluginHomeNoXdgWithDotConfig(t *testing.T) {
|
||||||
fSys := filesys.MakeFsInMemory()
|
fSys := filesys.MakeFsInMemory()
|
||||||
configDir := filepath.Join(
|
configDir := filepath.Join(
|
||||||
|
|||||||
@@ -11,26 +11,6 @@ import (
|
|||||||
|
|
||||||
func TestBasicIO_1(t *testing.T) {
|
func TestBasicIO_1(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
opts := th.MakeDefaultOptions()
|
|
||||||
if !opts.UseKyaml {
|
|
||||||
// This test won't pass under apimachinery, because in the bowels of
|
|
||||||
// that code (see GetAnnotations in v0.17.0 of
|
|
||||||
// k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go)
|
|
||||||
// an error returned from NestedStringMap is discarded, and an
|
|
||||||
// empty annotation map is silently returned, making this test fail
|
|
||||||
// The swallowed error arises from code like:
|
|
||||||
// var v interface{}
|
|
||||||
// v = true
|
|
||||||
// if str, ok := v.(string); ok {
|
|
||||||
// save the value in a map (doesn't happen)
|
|
||||||
// } else {
|
|
||||||
// return an error (that is then ignored by GetAnnotations)
|
|
||||||
// }
|
|
||||||
// The error happens when any annotation value can be interpreted as
|
|
||||||
// a boolean or number. Such annotations cannot be successfully applied
|
|
||||||
// to an object in a cluster unless they are quoted.
|
|
||||||
t.SkipNow()
|
|
||||||
}
|
|
||||||
th.WriteK(".", `
|
th.WriteK(".", `
|
||||||
resources:
|
resources:
|
||||||
- service.yaml
|
- service.yaml
|
||||||
@@ -47,7 +27,7 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
clusterIP: None
|
clusterIP: None
|
||||||
`)
|
`)
|
||||||
m := th.Run(".", opts)
|
m := th.Run(".", th.MakeDefaultOptions())
|
||||||
// The annotations are sorted by key, hence the order change.
|
// The annotations are sorted by key, hence the order change.
|
||||||
// Quotes are added intentionally.
|
// Quotes are added intentionally.
|
||||||
th.AssertActualEqualsExpected(
|
th.AssertActualEqualsExpected(
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package krusty_test
|
package krusty_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
@@ -176,7 +175,9 @@ weak nuclear
|
|||||||
`)
|
`)
|
||||||
opts := th.MakeDefaultOptions()
|
opts := th.MakeDefaultOptions()
|
||||||
m := th.Run(".", opts)
|
m := th.Run(".", opts)
|
||||||
expFmt := `apiVersion: v1
|
th.AssertActualEqualsExpected(
|
||||||
|
m, `
|
||||||
|
apiVersion: v1
|
||||||
data:
|
data:
|
||||||
BIRD: falcon
|
BIRD: falcon
|
||||||
MOUNTAIN: everest
|
MOUNTAIN: everest
|
||||||
@@ -212,32 +213,19 @@ data:
|
|||||||
BIRD: ZmFsY29u
|
BIRD: ZmFsY29u
|
||||||
MOUNTAIN: ZXZlcmVzdA==
|
MOUNTAIN: ZXZlcmVzdA==
|
||||||
OCEAN: cGFjaWZpYw==
|
OCEAN: cGFjaWZpYw==
|
||||||
forces.txt: %s
|
forces.txt: |
|
||||||
|
CmdyYXZpdGF0aW9uYWwKZWxlY3Ryb21hZ25ldGljCnN0cm9uZyBudWNsZWFyCndlYWsgbn
|
||||||
|
VjbGVhcgo=
|
||||||
fruit: YXBwbGU=
|
fruit: YXBwbGU=
|
||||||
passphrase: %s
|
passphrase: |
|
||||||
|
CkxpZmUgaXMgc2hvcnQuCkJ1dCB0aGUgeWVhcnMgYXJlIGxvbmcuCk5vdCB3aGlsZSB0aG
|
||||||
|
UgZXZpbCBkYXlzIGNvbWUgbm90Lgo=
|
||||||
vegetable: YnJvY2NvbGk=
|
vegetable: YnJvY2NvbGk=
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
name: blah-bob-%s
|
name: blah-bob-58g62h555c
|
||||||
type: Opaque
|
type: Opaque
|
||||||
`
|
`)
|
||||||
th.AssertActualEqualsExpected(
|
|
||||||
m,
|
|
||||||
// TODO(#3304): DECISION - kyaml better; not a bug.
|
|
||||||
opts.IfApiMachineryElseKyaml(
|
|
||||||
fmt.Sprintf(
|
|
||||||
expFmt,
|
|
||||||
`CmdyYXZpdGF0aW9uYWwKZWxlY3Ryb21hZ25ldGljCnN0cm9uZyBudWNsZWFyCndlYWsgbn
|
|
||||||
VjbGVhcgo=`,
|
|
||||||
`CkxpZmUgaXMgc2hvcnQuCkJ1dCB0aGUgeWVhcnMgYXJlIGxvbmcuCk5vdCB3aGlsZSB0aG
|
|
||||||
UgZXZpbCBkYXlzIGNvbWUgbm90Lgo=`,
|
|
||||||
`ftht6hfgmb`),
|
|
||||||
fmt.Sprintf(
|
|
||||||
expFmt, `|
|
|
||||||
CmdyYXZpdGF0aW9uYWwKZWxlY3Ryb21hZ25ldGljCnN0cm9uZyBudWNsZWFyCndlYWsgbn
|
|
||||||
VjbGVhcgo=`, `|
|
|
||||||
CkxpZmUgaXMgc2hvcnQuCkJ1dCB0aGUgeWVhcnMgYXJlIGxvbmcuCk5vdCB3aGlsZSB0aG
|
|
||||||
UgZXZpbCBkYXlzIGNvbWUgbm90Lgo=`, `58g62h555c`)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: These should be errors instead.
|
// TODO: These should be errors instead.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package krusty_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,5 +36,8 @@ spec:
|
|||||||
- name: PODINFO_UI_COLOR
|
- name: PODINFO_UI_COLOR
|
||||||
value: "#34577c"
|
value: "#34577c"
|
||||||
`)
|
`)
|
||||||
th.RunWithErr(".", th.MakeDefaultOptions())
|
m := th.Run(".", th.MakeDefaultOptions())
|
||||||
|
_, err := m.AsYaml()
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "mapping key \"env\" already defined")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestFnExecGenerator(t *testing.T) {
|
func TestFnExecGenerator(t *testing.T) {
|
||||||
th := kusttest_test.MakeEnhancedHarness(t)
|
// Function plugins should not need the env setup done by MakeEnhancedHarness
|
||||||
defer th.Reset()
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteK(".", `
|
th.WriteK(".", `
|
||||||
resources:
|
resources:
|
||||||
@@ -92,8 +92,8 @@ func skipIfNoDocker(t *testing.T) {
|
|||||||
func TestFnContainerGenerator(t *testing.T) {
|
func TestFnContainerGenerator(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
th := kusttest_test.MakeEnhancedHarness(t)
|
// Function plugins should not need the env setup done by MakeEnhancedHarness
|
||||||
defer th.Reset()
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteK(".", `
|
th.WriteK(".", `
|
||||||
resources:
|
resources:
|
||||||
@@ -308,8 +308,8 @@ spec:
|
|||||||
func TestFnContainerTransformer(t *testing.T) {
|
func TestFnContainerTransformer(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
th := kusttest_test.MakeEnhancedHarness(t)
|
// Function plugins should not need the env setup done by MakeEnhancedHarness
|
||||||
defer th.Reset()
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteK(".", `
|
th.WriteK(".", `
|
||||||
resources:
|
resources:
|
||||||
@@ -408,8 +408,8 @@ spec:
|
|||||||
func TestFnContainerTransformerWithConfig(t *testing.T) {
|
func TestFnContainerTransformerWithConfig(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
th := kusttest_test.MakeEnhancedHarness(t)
|
// Function plugins should not need the env setup done by MakeEnhancedHarness
|
||||||
defer th.Reset()
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteK(".", `
|
th.WriteK(".", `
|
||||||
resources:
|
resources:
|
||||||
@@ -468,8 +468,8 @@ metadata:
|
|||||||
func TestFnContainerEnvVars(t *testing.T) {
|
func TestFnContainerEnvVars(t *testing.T) {
|
||||||
skipIfNoDocker(t)
|
skipIfNoDocker(t)
|
||||||
|
|
||||||
th := kusttest_test.MakeEnhancedHarness(t)
|
// Function plugins should not need the env setup done by MakeEnhancedHarness
|
||||||
defer th.Reset()
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteK(".", `
|
th.WriteK(".", `
|
||||||
generators:
|
generators:
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
// GVKN shouldn't change with default options
|
// GVKN shouldn't change with default options
|
||||||
func TestKeepOriginalGVKN(t *testing.T) {
|
func TestKeepOriginalGVKN(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteF("deployment.yaml", `
|
th.WriteF("deployment.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -27,14 +26,12 @@ spec:
|
|||||||
- name: nginx
|
- name: nginx
|
||||||
image: nginx
|
image: nginx
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteF("patch.yaml", `
|
th.WriteF("patch.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: StatefulSet
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
name: new-name
|
name: new-name
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteK(".", `
|
th.WriteK(".", `
|
||||||
resources:
|
resources:
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
@@ -44,7 +41,6 @@ patches:
|
|||||||
target:
|
target:
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
`)
|
`)
|
||||||
|
|
||||||
m := th.Run(".", th.MakeDefaultOptions())
|
m := th.Run(".", th.MakeDefaultOptions())
|
||||||
th.AssertActualEqualsExpected(m, `
|
th.AssertActualEqualsExpected(m, `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
@@ -64,7 +60,6 @@ spec:
|
|||||||
// These tests document behavior that will change
|
// These tests document behavior that will change
|
||||||
func TestChangeName(t *testing.T) {
|
func TestChangeName(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteF("deployment.yaml", `
|
th.WriteF("deployment.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -77,14 +72,12 @@ spec:
|
|||||||
- name: nginx
|
- name: nginx
|
||||||
image: nginx
|
image: nginx
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteF("patch.yaml", `
|
th.WriteF("patch.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: new-name
|
name: new-name
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteK(".", `
|
th.WriteK(".", `
|
||||||
resources:
|
resources:
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
@@ -93,18 +86,16 @@ patches:
|
|||||||
- path: patch.yaml
|
- path: patch.yaml
|
||||||
target:
|
target:
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
options:
|
||||||
|
allowNameChange: true
|
||||||
`)
|
`)
|
||||||
|
|
||||||
options := th.MakeDefaultOptions()
|
options := th.MakeDefaultOptions()
|
||||||
options.AllowResourceIdChanges = true
|
|
||||||
|
|
||||||
// name should become `new-name`
|
|
||||||
m := th.Run(".", options)
|
m := th.Run(".", options)
|
||||||
th.AssertActualEqualsExpected(m, `
|
th.AssertActualEqualsExpected(m, `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: old-name
|
name: new-name
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
@@ -116,7 +107,6 @@ spec:
|
|||||||
|
|
||||||
func TestChangeKind(t *testing.T) {
|
func TestChangeKind(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteF("deployment.yaml", `
|
th.WriteF("deployment.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -129,32 +119,27 @@ spec:
|
|||||||
- name: nginx
|
- name: nginx
|
||||||
image: nginx
|
image: nginx
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteF("patch.yaml", `
|
th.WriteF("patch.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: StatefulSet
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
name: old-name
|
name: old-name
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteK(".", `
|
th.WriteK(".", `
|
||||||
resources:
|
resources:
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
|
|
||||||
patches:
|
patches:
|
||||||
- path: patch.yaml
|
- path: patch.yaml
|
||||||
target:
|
target:
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
options:
|
||||||
|
allowKindChange: true
|
||||||
`)
|
`)
|
||||||
|
|
||||||
options := th.MakeDefaultOptions()
|
options := th.MakeDefaultOptions()
|
||||||
options.AllowResourceIdChanges = true
|
|
||||||
|
|
||||||
// kind should become `StatefulSet`
|
|
||||||
m := th.Run(".", options)
|
m := th.Run(".", options)
|
||||||
th.AssertActualEqualsExpected(m, `
|
th.AssertActualEqualsExpected(m, `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
name: old-name
|
name: old-name
|
||||||
spec:
|
spec:
|
||||||
@@ -168,7 +153,6 @@ spec:
|
|||||||
|
|
||||||
func TestChangeNameAndKind(t *testing.T) {
|
func TestChangeNameAndKind(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteF("deployment.yaml", `
|
th.WriteF("deployment.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -181,35 +165,30 @@ spec:
|
|||||||
- name: nginx
|
- name: nginx
|
||||||
image: nginx
|
image: nginx
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteF("patch.yaml", `
|
th.WriteF("patch.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: StatefulSet
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
name: new-name
|
name: new-name
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteK(".", `
|
th.WriteK(".", `
|
||||||
resources:
|
resources:
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
|
|
||||||
patches:
|
patches:
|
||||||
- path: patch.yaml
|
- path: patch.yaml
|
||||||
target:
|
target:
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
options:
|
||||||
|
allowNameChange: true
|
||||||
|
allowKindChange: true
|
||||||
`)
|
`)
|
||||||
|
|
||||||
options := th.MakeDefaultOptions()
|
options := th.MakeDefaultOptions()
|
||||||
options.AllowResourceIdChanges = true
|
|
||||||
|
|
||||||
// kind should become `StatefulSet`
|
|
||||||
// name should become `new-name`
|
|
||||||
m := th.Run(".", options)
|
m := th.Run(".", options)
|
||||||
th.AssertActualEqualsExpected(m, `
|
th.AssertActualEqualsExpected(m, `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
name: old-name
|
name: new-name
|
||||||
spec:
|
spec:
|
||||||
template:
|
template:
|
||||||
spec:
|
spec:
|
||||||
@@ -219,12 +198,10 @@ spec:
|
|||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/kubernetes-sigs/kustomize/issues/3280
|
|
||||||
// Should be able to refer to a resource with either its
|
// Should be able to refer to a resource with either its
|
||||||
// original GVKN or its current one
|
// original GVKN or its current one
|
||||||
func TestPatchOriginalName(t *testing.T) {
|
func TestPatchOriginalName(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteF("base/deployment.yaml", `
|
th.WriteF("base/deployment.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -246,13 +223,13 @@ metadata:
|
|||||||
th.WriteK("base", `
|
th.WriteK("base", `
|
||||||
resources:
|
resources:
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
|
|
||||||
patches:
|
patches:
|
||||||
- path: patch.yaml
|
- path: patch.yaml
|
||||||
target:
|
target:
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
options:
|
||||||
|
allowNameChange: true
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteK("overlay", `
|
th.WriteK("overlay", `
|
||||||
resources:
|
resources:
|
||||||
- ../base
|
- ../base
|
||||||
@@ -267,17 +244,13 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
replicas: 999
|
replicas: 999
|
||||||
`)
|
`)
|
||||||
|
|
||||||
options := th.MakeDefaultOptions()
|
options := th.MakeDefaultOptions()
|
||||||
options.AllowResourceIdChanges = true
|
|
||||||
|
|
||||||
// name should become `new-name`
|
|
||||||
m := th.Run("overlay", options)
|
m := th.Run("overlay", options)
|
||||||
th.AssertActualEqualsExpected(m, `
|
th.AssertActualEqualsExpected(m, `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: old-name
|
name: new-name
|
||||||
spec:
|
spec:
|
||||||
replicas: 999
|
replicas: 999
|
||||||
template:
|
template:
|
||||||
@@ -290,7 +263,6 @@ spec:
|
|||||||
|
|
||||||
func TestPatchNewName(t *testing.T) {
|
func TestPatchNewName(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteF("base/deployment.yaml", `
|
th.WriteF("base/deployment.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -312,13 +284,13 @@ metadata:
|
|||||||
th.WriteK("base", `
|
th.WriteK("base", `
|
||||||
resources:
|
resources:
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
|
|
||||||
patches:
|
patches:
|
||||||
- path: patch.yaml
|
- path: patch.yaml
|
||||||
target:
|
target:
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
options:
|
||||||
|
allowNameChange: true
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteK("overlay", `
|
th.WriteK("overlay", `
|
||||||
resources:
|
resources:
|
||||||
- ../base
|
- ../base
|
||||||
@@ -333,18 +305,25 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
replicas: 999
|
replicas: 999
|
||||||
`)
|
`)
|
||||||
|
|
||||||
options := th.MakeDefaultOptions()
|
options := th.MakeDefaultOptions()
|
||||||
options.AllowResourceIdChanges = true
|
m := th.Run("overlay", options)
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
// depPatch cannot find target with the name `new-name`
|
apiVersion: v1
|
||||||
// because base/patch.yaml can't yet edit the name
|
kind: Deployment
|
||||||
assert.Error(t, th.RunWithErr("overlay", options))
|
metadata:
|
||||||
|
name: new-name
|
||||||
|
spec:
|
||||||
|
replicas: 999
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx
|
||||||
|
name: nginx
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPatchOriginalNameAndKind(t *testing.T) {
|
func TestPatchOriginalNameAndKind(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteF("base/deployment.yaml", `
|
th.WriteF("base/deployment.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -366,13 +345,14 @@ metadata:
|
|||||||
th.WriteK("base", `
|
th.WriteK("base", `
|
||||||
resources:
|
resources:
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
|
|
||||||
patches:
|
patches:
|
||||||
- path: patch.yaml
|
- path: patch.yaml
|
||||||
target:
|
target:
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
options:
|
||||||
|
allowNameChange: true
|
||||||
|
allowKindChange: true
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteK("overlay", `
|
th.WriteK("overlay", `
|
||||||
resources:
|
resources:
|
||||||
- ../base
|
- ../base
|
||||||
@@ -387,18 +367,13 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
replicas: 999
|
replicas: 999
|
||||||
`)
|
`)
|
||||||
|
|
||||||
options := th.MakeDefaultOptions()
|
options := th.MakeDefaultOptions()
|
||||||
options.AllowResourceIdChanges = true
|
|
||||||
|
|
||||||
// kind should become `StatefulSet`
|
|
||||||
// name should become `new-name`
|
|
||||||
m := th.Run("overlay", options)
|
m := th.Run("overlay", options)
|
||||||
th.AssertActualEqualsExpected(m, `
|
th.AssertActualEqualsExpected(m, `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
name: old-name
|
name: new-name
|
||||||
spec:
|
spec:
|
||||||
replicas: 999
|
replicas: 999
|
||||||
template:
|
template:
|
||||||
@@ -411,7 +386,6 @@ spec:
|
|||||||
|
|
||||||
func TestPatchNewNameAndKind(t *testing.T) {
|
func TestPatchNewNameAndKind(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteF("base/deployment.yaml", `
|
th.WriteF("base/deployment.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -433,13 +407,14 @@ metadata:
|
|||||||
th.WriteK("base", `
|
th.WriteK("base", `
|
||||||
resources:
|
resources:
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
|
|
||||||
patches:
|
patches:
|
||||||
- path: patch.yaml
|
- path: patch.yaml
|
||||||
target:
|
target:
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
options:
|
||||||
|
allowNameChange: true
|
||||||
|
allowKindChange: true
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteK("overlay", `
|
th.WriteK("overlay", `
|
||||||
resources:
|
resources:
|
||||||
- ../base
|
- ../base
|
||||||
@@ -454,20 +429,27 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
replicas: 999
|
replicas: 999
|
||||||
`)
|
`)
|
||||||
|
|
||||||
options := th.MakeDefaultOptions()
|
options := th.MakeDefaultOptions()
|
||||||
options.AllowResourceIdChanges = true
|
m := th.Run("overlay", options)
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
// depPatch cannot find target with kind `StatefulSet` and name `new-name`
|
apiVersion: v1
|
||||||
// because base/patch.yaml can't yet edit the kind or name
|
kind: StatefulSet
|
||||||
assert.Error(t, th.RunWithErr("overlay", options))
|
metadata:
|
||||||
|
name: new-name
|
||||||
|
spec:
|
||||||
|
replicas: 999
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx
|
||||||
|
name: nginx
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use original name, but new kind
|
// Use original name, but new kind
|
||||||
// Should fail, even after #3280 is done, because this ID is invalid
|
// Should fail, even after #3280 is done, because this ID is invalid
|
||||||
func TestPatchOriginalNameAndNewKind(t *testing.T) {
|
func TestPatchOriginalNameAndNewKind(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
|
||||||
th.WriteF("base/deployment.yaml", `
|
th.WriteF("base/deployment.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
@@ -489,13 +471,14 @@ metadata:
|
|||||||
th.WriteK("base", `
|
th.WriteK("base", `
|
||||||
resources:
|
resources:
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
|
|
||||||
patches:
|
patches:
|
||||||
- path: patch.yaml
|
- path: patch.yaml
|
||||||
target:
|
target:
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
options:
|
||||||
|
allowNameChange: true
|
||||||
|
allowKindChange: true
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteK("overlay", `
|
th.WriteK("overlay", `
|
||||||
resources:
|
resources:
|
||||||
- ../base
|
- ../base
|
||||||
@@ -510,10 +493,7 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
replicas: 999
|
replicas: 999
|
||||||
`)
|
`)
|
||||||
|
|
||||||
options := th.MakeDefaultOptions()
|
options := th.MakeDefaultOptions()
|
||||||
options.AllowResourceIdChanges = true
|
|
||||||
|
|
||||||
// depPatch cannot find target with kind `Deployment` and name `new-name`
|
// depPatch cannot find target with kind `Deployment` and name `new-name`
|
||||||
// because the resource never had this GVKN
|
// because the resource never had this GVKN
|
||||||
assert.Error(t, th.RunWithErr("overlay", options))
|
assert.Error(t, th.RunWithErr("overlay", options))
|
||||||
@@ -552,12 +532,11 @@ spec:
|
|||||||
|
|
||||||
func TestBaseReuseNameAndKindConflict(t *testing.T) {
|
func TestBaseReuseNameAndKindConflict(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
th.WriteK("shared", `
|
||||||
th.WriteK("/app/shared", `
|
|
||||||
resources:
|
resources:
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
`)
|
`)
|
||||||
th.WriteF("/app/shared/deployment.yaml", `
|
th.WriteF("shared/deployment.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -570,53 +549,139 @@ spec:
|
|||||||
image: nginx
|
image: nginx
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteK("/app/component1/base", `
|
th.WriteK("component1/base", `
|
||||||
resources:
|
resources:
|
||||||
- ../../shared
|
- ../../shared
|
||||||
`)
|
`)
|
||||||
th.WriteK("/app/component1/overlay", `
|
th.WriteK("component1/overlay", `
|
||||||
resources:
|
resources:
|
||||||
- ../base
|
- ../base
|
||||||
|
|
||||||
namePrefix: overlay-
|
namePrefix: overlay-
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteK("/app/component2/base", `
|
th.WriteK("component2/base", `
|
||||||
resources:
|
resources:
|
||||||
- ../../shared
|
- ../../shared
|
||||||
|
|
||||||
patches:
|
patches:
|
||||||
- path: patch.yaml
|
- path: patch.yaml
|
||||||
target:
|
target:
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
name: my-deploy
|
name: my-deploy
|
||||||
|
options:
|
||||||
|
allowNameChange: true
|
||||||
|
allowKindChange: true
|
||||||
`)
|
`)
|
||||||
th.WriteF("/app/component2/base/patch.yaml", `
|
th.WriteF("component2/base/patch.yaml", `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: StatefulSet
|
kind: StatefulSet
|
||||||
metadata:
|
metadata:
|
||||||
name: my-stateful-set
|
name: my-stateful-set
|
||||||
`)
|
`)
|
||||||
th.WriteK("/app/component2/overlay", `
|
th.WriteK("component2/overlay", `
|
||||||
resources:
|
resources:
|
||||||
- ../base
|
- ../base
|
||||||
|
|
||||||
namePrefix: overlay-
|
namePrefix: overlay-
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteK("/app", `
|
th.WriteK(".", `
|
||||||
resources:
|
resources:
|
||||||
- component1/overlay
|
- component1/overlay
|
||||||
- component2/overlay
|
- component2/overlay
|
||||||
`)
|
`)
|
||||||
|
|
||||||
options := th.MakeDefaultOptions()
|
options := th.MakeDefaultOptions()
|
||||||
options.AllowResourceIdChanges = true
|
m := th.Run(".", options)
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
// Error occurs when app/component2/base tries to load the shared resources
|
apiVersion: v1
|
||||||
// because the kind is not (yet) allowed to change yet
|
kind: Deployment
|
||||||
// so it loads a second Deployment with the name my-deploy
|
metadata:
|
||||||
// instead of a StatefulSet as specified by the patch.
|
name: overlay-my-deploy
|
||||||
// Will be fixed by #3280.
|
spec:
|
||||||
assert.Error(t, th.RunWithErr("overlay", options))
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx
|
||||||
|
name: nginx
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: overlay-my-stateful-set
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- image: nginx
|
||||||
|
name: nginx
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNameReferenceAfterGvknChange(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
th.WriteF("configMap.yaml", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: old-name
|
||||||
|
`)
|
||||||
|
th.WriteF("patch.yaml", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: new-name
|
||||||
|
`)
|
||||||
|
th.WriteF("deployment.yaml", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
name: old-name
|
||||||
|
key: somekey
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: old-name
|
||||||
|
key: somekey
|
||||||
|
`)
|
||||||
|
th.WriteK(".", `
|
||||||
|
resources:
|
||||||
|
- configMap.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
patches:
|
||||||
|
- path: patch.yaml
|
||||||
|
target:
|
||||||
|
kind: ConfigMap
|
||||||
|
options:
|
||||||
|
allowNameChange: true
|
||||||
|
`)
|
||||||
|
m := th.Run(".", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: new-name
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deploy
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- env:
|
||||||
|
- valueFrom:
|
||||||
|
configMapKeyRef:
|
||||||
|
key: somekey
|
||||||
|
name: new-name
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
key: somekey
|
||||||
|
name: new-name
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
|
|
||||||
package krusty_test
|
package krusty_test
|
||||||
|
|
||||||
/*
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
)
|
)
|
||||||
|
|
||||||
var expected string = `
|
const expectedHelm = `
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
data:
|
data:
|
||||||
rcon-password: Q0hBTkdFTUUh
|
rcon-password: Q0hBTkdFTUUh
|
||||||
@@ -18,36 +17,19 @@ kind: Secret
|
|||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: test-minecraft
|
app: test-minecraft
|
||||||
chart: minecraft-1.2.0
|
chart: minecraft-3.1.3
|
||||||
heritage: Helm
|
heritage: Helm
|
||||||
release: test
|
release: test
|
||||||
name: test-minecraft
|
name: test-minecraft
|
||||||
type: Opaque
|
type: Opaque
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: PersistentVolumeClaim
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
volume.alpha.kubernetes.io/storage-class: default
|
|
||||||
labels:
|
|
||||||
app: test-minecraft
|
|
||||||
chart: minecraft-1.2.0
|
|
||||||
heritage: Helm
|
|
||||||
release: test
|
|
||||||
name: test-minecraft-datadir
|
|
||||||
spec:
|
|
||||||
accessModes:
|
|
||||||
- ReadWriteOnce
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 1Gi
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
|
annotations: {}
|
||||||
labels:
|
labels:
|
||||||
app: test-minecraft
|
app: test-minecraft
|
||||||
chart: minecraft-1.2.0
|
chart: minecraft-3.1.3
|
||||||
heritage: Helm
|
heritage: Helm
|
||||||
release: test
|
release: test
|
||||||
name: test-minecraft
|
name: test-minecraft
|
||||||
@@ -59,45 +41,43 @@ spec:
|
|||||||
targetPort: minecraft
|
targetPort: minecraft
|
||||||
selector:
|
selector:
|
||||||
app: test-minecraft
|
app: test-minecraft
|
||||||
type: LoadBalancer
|
type: ClusterIP
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestHelmChartInflationGenerator(t *testing.T) {
|
func TestHelmChartInflationGeneratorOld(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t)
|
||||||
th.WriteK("/app", `
|
defer th.Reset()
|
||||||
|
if err := th.ErrIfNoHelm(); err != nil {
|
||||||
|
t.Skip("skipping: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
th.WriteK(th.GetRoot(), `
|
||||||
helmChartInflationGenerator:
|
helmChartInflationGenerator:
|
||||||
- chartName: minecraft
|
- chartName: minecraft
|
||||||
chartRepoUrl: https://kubernetes-charts.storage.googleapis.com
|
chartRepoUrl: https://itzg.github.io/minecraft-server-charts
|
||||||
chartVersion: v1.2.0
|
chartVersion: 3.1.3
|
||||||
releaseName: test
|
releaseName: test
|
||||||
releaseNamespace: testNamespace
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
m := th.Run("/app", th.MakeDefaultOptions())
|
m := th.Run(th.GetRoot(), th.MakeOptionsPluginsEnabled())
|
||||||
th.AssertActualEqualsExpected(m, expected)
|
th.AssertActualEqualsExpected(m, expectedHelm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHelmChartInflationGenerator(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeEnhancedHarnessWithTmpRoot(t)
|
||||||
|
defer th.Reset()
|
||||||
|
if err := th.ErrIfNoHelm(); err != nil {
|
||||||
|
t.Skip("skipping: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
func TestHelmChartInflationGeneratorAsPlugin(t *testing.T) {
|
th.WriteK(th.GetRoot(), `
|
||||||
th := kusttest_test.MakeHarness(t)
|
helmCharts:
|
||||||
th.WriteK("/app", `
|
- name: minecraft
|
||||||
generators:
|
repo: https://itzg.github.io/minecraft-server-charts
|
||||||
- helm.yaml
|
version: 3.1.3
|
||||||
|
releaseName: test
|
||||||
`)
|
`)
|
||||||
|
|
||||||
th.WriteF("/app/helm.yaml", `
|
m := th.Run(th.GetRoot(), th.MakeOptionsPluginsEnabled())
|
||||||
apiVersion: builtin
|
th.AssertActualEqualsExpected(m, expectedHelm)
|
||||||
kind: HelmChartInflationGenerator
|
|
||||||
metadata:
|
|
||||||
name: myMap
|
|
||||||
chartName: minecraft
|
|
||||||
chartRepoUrl: https://kubernetes-charts.storage.googleapis.com
|
|
||||||
chartVersion: v1.2.0
|
|
||||||
releaseName: test
|
|
||||||
releaseNamespace: testNamespace
|
|
||||||
`)
|
|
||||||
|
|
||||||
m := th.Run("/app", th.MakeDefaultOptions())
|
|
||||||
th.AssertActualEqualsExpected(m, expected)
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|||||||
93
api/krusty/inlinelabels_test.go
Normal file
93
api/krusty/inlinelabels_test.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package krusty_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
|
)
|
||||||
|
|
||||||
|
const resources string = `apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: my-deployment
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: my-deployment
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
---
|
||||||
|
apiVersion: example.dev/v1
|
||||||
|
kind: MyCRD
|
||||||
|
metadata:
|
||||||
|
name: crd
|
||||||
|
`
|
||||||
|
|
||||||
|
func TestKustomizationLabels(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
makeResourcesForPatchTest(th)
|
||||||
|
th.WriteK("/app", `
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
|
||||||
|
labels:
|
||||||
|
- pairs:
|
||||||
|
foo: bar
|
||||||
|
- pairs:
|
||||||
|
a: b
|
||||||
|
includeSelectors: true
|
||||||
|
- pairs:
|
||||||
|
c: d
|
||||||
|
fields:
|
||||||
|
- path: spec/selector
|
||||||
|
group: example.dev
|
||||||
|
version: v1
|
||||||
|
kind: MyCRD
|
||||||
|
create: true
|
||||||
|
`)
|
||||||
|
th.WriteF("/app/deployment.yaml", resources)
|
||||||
|
m := th.Run("/app", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
a: b
|
||||||
|
c: d
|
||||||
|
foo: bar
|
||||||
|
name: my-deployment
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
a: b
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
a: b
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
name: my-deployment
|
||||||
|
---
|
||||||
|
apiVersion: example.dev/v1
|
||||||
|
kind: MyCRD
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
a: b
|
||||||
|
c: d
|
||||||
|
foo: bar
|
||||||
|
name: crd
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
c: d
|
||||||
|
`)
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ type Kustomizer struct {
|
|||||||
func MakeKustomizer(o *Options) *Kustomizer {
|
func MakeKustomizer(o *Options) *Kustomizer {
|
||||||
return &Kustomizer{
|
return &Kustomizer{
|
||||||
options: o,
|
options: o,
|
||||||
depProvider: provider.NewDepProvider(o.UseKyaml),
|
depProvider: provider.NewDepProvider(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,9 +52,7 @@ func MakeKustomizer(o *Options) *Kustomizer {
|
|||||||
// and Run can be called on each of them).
|
// and Run can be called on each of them).
|
||||||
func (b *Kustomizer) Run(
|
func (b *Kustomizer) Run(
|
||||||
fSys filesys.FileSystem, path string) (resmap.ResMap, error) {
|
fSys filesys.FileSystem, path string) (resmap.ResMap, error) {
|
||||||
resmapFactory := resmap.NewFactory(
|
resmapFactory := resmap.NewFactory(b.depProvider.GetResourceFactory())
|
||||||
b.depProvider.GetResourceFactory(),
|
|
||||||
b.depProvider.GetConflictDetectorFactory())
|
|
||||||
lr := fLdr.RestrictionNone
|
lr := fLdr.RestrictionNone
|
||||||
if b.options.LoadRestrictions == types.LoadRestrictionsRootOnly {
|
if b.options.LoadRestrictions == types.LoadRestrictionsRootOnly {
|
||||||
lr = fLdr.RestrictionRootOnly
|
lr = fLdr.RestrictionRootOnly
|
||||||
@@ -68,7 +66,8 @@ func (b *Kustomizer) Run(
|
|||||||
ldr,
|
ldr,
|
||||||
b.depProvider.GetFieldValidator(),
|
b.depProvider.GetFieldValidator(),
|
||||||
resmapFactory,
|
resmapFactory,
|
||||||
pLdr.NewLoader(b.options.PluginConfig, resmapFactory),
|
// The plugin configs are always located on disk, regardless of the fSys passed in
|
||||||
|
pLdr.NewLoader(b.options.PluginConfig, resmapFactory, filesys.MakeFsOnDisk()),
|
||||||
)
|
)
|
||||||
err = kt.Load()
|
err = kt.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
package krusty_test
|
package krusty_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
@@ -237,6 +235,7 @@ metadata:
|
|||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Goal is to remove " emptyDir: {}" with a patch.
|
||||||
func TestRemoveEmptyDirWithPatchesAtSameLevel(t *testing.T) {
|
func TestRemoveEmptyDirWithPatchesAtSameLevel(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
th.WriteK("base", `
|
th.WriteK("base", `
|
||||||
@@ -300,7 +299,9 @@ spec:
|
|||||||
`)
|
`)
|
||||||
opts := th.MakeDefaultOptions()
|
opts := th.MakeDefaultOptions()
|
||||||
m := th.Run("overlay", opts)
|
m := th.Run("overlay", opts)
|
||||||
expFmt := `apiVersion: apps/v1
|
th.AssertActualEqualsExpected(
|
||||||
|
m, `
|
||||||
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: nginx
|
name: nginx
|
||||||
@@ -315,21 +316,11 @@ spec:
|
|||||||
name: nginx
|
name: nginx
|
||||||
- image: sidecar:latest
|
- image: sidecar:latest
|
||||||
name: sidecar
|
name: sidecar
|
||||||
volumes:%s
|
volumes:
|
||||||
name: nginx-persistent-storage
|
|
||||||
`
|
|
||||||
th.AssertActualEqualsExpected(
|
|
||||||
// TODO(#3394): Should be possible to delete emptyDir with a patch.
|
|
||||||
// TODO(#3304): DECISION - still a bug, emptyDir should be deleted.
|
|
||||||
m, opts.IfApiMachineryElseKyaml(
|
|
||||||
fmt.Sprintf(expFmt, `
|
|
||||||
- gcePersistentDisk:
|
- gcePersistentDisk:
|
||||||
pdName: nginx-persistent-storage`),
|
pdName: nginx-persistent-storage
|
||||||
fmt.Sprintf(expFmt, `
|
name: nginx-persistent-storage
|
||||||
- emptyDir: {}
|
`)
|
||||||
gcePersistentDisk:
|
|
||||||
pdName: nginx-persistent-storage`),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSimpleMultiplePatches(t *testing.T) {
|
func TestSimpleMultiplePatches(t *testing.T) {
|
||||||
@@ -715,7 +706,7 @@ metadata:
|
|||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiplePatchesWithConflict(t *testing.T) {
|
func TestNonCommutablePatches(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
makeCommonFilesForMultiplePatchTests(th)
|
makeCommonFilesForMultiplePatchTests(th)
|
||||||
th.WriteF("overlay/staging/deployment-patch1.yaml", `
|
th.WriteF("overlay/staging/deployment-patch1.yaml", `
|
||||||
@@ -753,12 +744,10 @@ spec:
|
|||||||
- name: ENABLE_FEATURE_FOO
|
- name: ENABLE_FEATURE_FOO
|
||||||
value: FALSE
|
value: FALSE
|
||||||
`)
|
`)
|
||||||
opts := th.MakeDefaultOptions()
|
// kyaml doesn't try to detect conflicts in patches
|
||||||
if opts.UseKyaml {
|
// (so ENABLE_FEATURE_FOO FALSE wins).
|
||||||
// kyaml doesn't try to detect conflicts in patches
|
m := th.Run("overlay/staging", th.MakeDefaultOptions())
|
||||||
// (so ENABLE_FEATURE_FOO FALSE wins).
|
th.AssertActualEqualsExpected(m, `
|
||||||
m := th.Run("overlay/staging", opts)
|
|
||||||
th.AssertActualEqualsExpected(m, `
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -852,16 +841,6 @@ metadata:
|
|||||||
env: staging
|
env: staging
|
||||||
name: staging-configmap-in-overlay-dc6fm46dhm
|
name: staging-configmap-in-overlay-dc6fm46dhm
|
||||||
`)
|
`)
|
||||||
} else {
|
|
||||||
err := th.RunWithErr("overlay/staging", opts)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected conflict")
|
|
||||||
}
|
|
||||||
if !strings.Contains(
|
|
||||||
err.Error(), "conflict between ") {
|
|
||||||
t.Fatalf("Unexpected err: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMultiplePatchesWithOnePatchDeleteDirective(t *testing.T) {
|
func TestMultiplePatchesWithOnePatchDeleteDirective(t *testing.T) {
|
||||||
@@ -889,25 +868,23 @@ spec:
|
|||||||
- $patch: delete
|
- $patch: delete
|
||||||
name: sidecar
|
name: sidecar
|
||||||
`
|
`
|
||||||
cases := []struct {
|
cases := map[string]struct {
|
||||||
name string
|
|
||||||
patch1 string
|
patch1 string
|
||||||
patch2 string
|
patch2 string
|
||||||
expectError bool
|
expectError bool
|
||||||
}{
|
}{
|
||||||
{
|
"Patch with delete directive first": {
|
||||||
name: "Patch with delete directive first",
|
|
||||||
patch1: deletePatch,
|
patch1: deletePatch,
|
||||||
patch2: additivePatch,
|
patch2: additivePatch,
|
||||||
},
|
},
|
||||||
{
|
"Patch with delete directive second": {
|
||||||
name: "Patch with delete directive second",
|
|
||||||
patch1: additivePatch,
|
patch1: additivePatch,
|
||||||
patch2: deletePatch,
|
patch2: deletePatch,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for name := range cases {
|
||||||
t.Run(c.name, func(t *testing.T) {
|
c := cases[name]
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
makeCommonFilesForMultiplePatchTests(th)
|
makeCommonFilesForMultiplePatchTests(th)
|
||||||
th.WriteF("overlay/staging/deployment-patch1.yaml", c.patch1)
|
th.WriteF("overlay/staging/deployment-patch1.yaml", c.patch1)
|
||||||
@@ -1029,12 +1006,10 @@ spec:
|
|||||||
- $patch: delete
|
- $patch: delete
|
||||||
name: nginx
|
name: nginx
|
||||||
`)
|
`)
|
||||||
opt := th.MakeDefaultOptions()
|
// kyaml doesn't fail on conflicts in patches; both containers
|
||||||
if opt.UseKyaml {
|
// (nginx and sidecar) are deleted per this patching instruction.
|
||||||
// kyaml doesn't fail on conflicts in patches; both containers
|
m := th.Run("overlay/staging", th.MakeDefaultOptions())
|
||||||
// (nginx and sidecar) are deleted per this patching instruction.
|
th.AssertActualEqualsExpected(m, `
|
||||||
m := th.Run("overlay/staging", opt)
|
|
||||||
th.AssertActualEqualsExpected(m, `
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@@ -1112,17 +1087,6 @@ metadata:
|
|||||||
env: staging
|
env: staging
|
||||||
name: staging-configmap-in-overlay-dc6fm46dhm
|
name: staging-configmap-in-overlay-dc6fm46dhm
|
||||||
`)
|
`)
|
||||||
} else {
|
|
||||||
// No kyaml means error on a patch conflict.
|
|
||||||
err := th.RunWithErr("overlay/staging", opt)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected error")
|
|
||||||
}
|
|
||||||
if !strings.Contains(
|
|
||||||
err.Error(), "both containing ") {
|
|
||||||
t.Fatalf("Unexpected err: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// test for #3513
|
// test for #3513
|
||||||
@@ -1191,26 +1155,26 @@ spec:
|
|||||||
- image: dashicorp/consul:1.9.1
|
- image: dashicorp/consul:1.9.1
|
||||||
name: consul
|
name: consul
|
||||||
ports:
|
ports:
|
||||||
|
- containerPort: 8500
|
||||||
|
name: http
|
||||||
|
- containerPort: 8501
|
||||||
|
name: https
|
||||||
- containerPort: 8301
|
- containerPort: 8301
|
||||||
name: serflan-tcp
|
name: serflan-tcp
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
- containerPort: 8301
|
- containerPort: 8301
|
||||||
name: serflan-udp
|
name: serflan-udp
|
||||||
protocol: UDP
|
protocol: UDP
|
||||||
|
- containerPort: 8302
|
||||||
|
name: serfwan
|
||||||
|
- containerPort: 8300
|
||||||
|
name: server
|
||||||
- containerPort: 8600
|
- containerPort: 8600
|
||||||
name: dns-tcp
|
name: dns-tcp
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
- containerPort: 8600
|
- containerPort: 8600
|
||||||
name: dns-udp
|
name: dns-udp
|
||||||
protocol: UDP
|
protocol: UDP
|
||||||
- containerPort: 8500
|
|
||||||
name: http
|
|
||||||
- containerPort: 8501
|
|
||||||
name: https
|
|
||||||
- containerPort: 8302
|
|
||||||
name: serfwan
|
|
||||||
- containerPort: 8300
|
|
||||||
name: server
|
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1262,3 +1226,113 @@ spec:
|
|||||||
- name: rabbitmq.rules
|
- name: rabbitmq.rules
|
||||||
`)
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test for #3620
|
||||||
|
func TestPatchPortHasNoProtocol(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
th.WriteK(".", `
|
||||||
|
resources:
|
||||||
|
- service.yaml
|
||||||
|
patchesStrategicMerge:
|
||||||
|
- patch.yaml
|
||||||
|
`)
|
||||||
|
th.WriteF("service.yaml", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: web
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 30900
|
||||||
|
targetPort: 30900
|
||||||
|
protocol: TCP
|
||||||
|
type: NodePort
|
||||||
|
`)
|
||||||
|
th.WriteF("patch.yaml", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: web
|
||||||
|
labels:
|
||||||
|
service: web
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 30900
|
||||||
|
targetPort: 30900
|
||||||
|
selector:
|
||||||
|
service: web
|
||||||
|
`)
|
||||||
|
m := th.Run(".", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
service: web
|
||||||
|
name: web
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 30900
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 30900
|
||||||
|
selector:
|
||||||
|
service: web
|
||||||
|
type: NodePort
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test for #3620
|
||||||
|
func TestPatchAddNewServicePort(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
th.WriteK(".", `
|
||||||
|
resources:
|
||||||
|
- service.yaml
|
||||||
|
patchesStrategicMerge:
|
||||||
|
- patch.yaml
|
||||||
|
`)
|
||||||
|
th.WriteF("service.yaml", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: web
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 30900
|
||||||
|
targetPort: 30900
|
||||||
|
protocol: TCP
|
||||||
|
type: NodePort
|
||||||
|
`)
|
||||||
|
th.WriteF("patch.yaml", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: web
|
||||||
|
labels:
|
||||||
|
service: web
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 30901
|
||||||
|
targetPort: 30901
|
||||||
|
selector:
|
||||||
|
service: web
|
||||||
|
`)
|
||||||
|
m := th.Run(".", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
service: web
|
||||||
|
name: web
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- port: 30901
|
||||||
|
targetPort: 30901
|
||||||
|
- port: 30900
|
||||||
|
protocol: TCP
|
||||||
|
targetPort: 30900
|
||||||
|
selector:
|
||||||
|
service: web
|
||||||
|
type: NodePort
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package krusty_test
|
package krusty_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
@@ -265,9 +264,9 @@ kind: ServiceAccount
|
|||||||
metadata:
|
metadata:
|
||||||
name: external-dns
|
name: external-dns
|
||||||
`)
|
`)
|
||||||
opts := th.MakeDefaultOptions()
|
m := th.Run(".", th.MakeDefaultOptions())
|
||||||
m := th.Run(".", opts)
|
th.AssertActualEqualsExpected(
|
||||||
expFmt := `
|
m, `
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
metadata:
|
metadata:
|
||||||
@@ -353,7 +352,7 @@ spec:
|
|||||||
volumes:
|
volumes:
|
||||||
- name: azure-config-file
|
- name: azure-config-file
|
||||||
secret:
|
secret:
|
||||||
secretName: azure-config-file-%s
|
secretName: azure-config-file-66cc4224mm
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
@@ -366,13 +365,18 @@ metadata:
|
|||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
data:
|
data:
|
||||||
azure.json: %s
|
azure.json: |
|
||||||
|
ewoJInRlbmFudElkIjogIlhYWFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIiwKCS
|
||||||
|
JzdWJzY3JpcHRpb25JZCI6ICJYWFhYWC1YWFhYWFgtWFhYWFgtWFhYWFhYLVhYWFhYWCIs
|
||||||
|
CgkicmVzb3VyY2VHcm91cCI6ICJETlMtRVVXLVhYWC1SRyIsCgkidXNlTWFuYWdlZElkZW
|
||||||
|
50aXR5RXh0ZW5zaW9uIjogdHJ1ZSwKCSJ1c2VyQXNzaWduZWRJZGVudGl0eUlEIjogIlhY
|
||||||
|
WFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIgp9Cg==
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: external-dns
|
app: external-dns
|
||||||
instance: public
|
instance: public
|
||||||
name: azure-config-file-%s
|
name: azure-config-file-66cc4224mm
|
||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
type: Opaque
|
type: Opaque
|
||||||
---
|
---
|
||||||
@@ -461,7 +465,7 @@ spec:
|
|||||||
volumes:
|
volumes:
|
||||||
- name: azure-config-file
|
- name: azure-config-file
|
||||||
secret:
|
secret:
|
||||||
secretName: azure-config-file-private-%s
|
secretName: azure-config-file-private-66cc4224mm
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
@@ -474,42 +478,21 @@ metadata:
|
|||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
data:
|
data:
|
||||||
azure.json: %s
|
azure.json: |
|
||||||
|
ewoJInRlbmFudElkIjogIlhYWFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIiwKCS
|
||||||
|
JzdWJzY3JpcHRpb25JZCI6ICJYWFhYWC1YWFhYWFgtWFhYWFgtWFhYWFhYLVhYWFhYWCIs
|
||||||
|
CgkicmVzb3VyY2VHcm91cCI6ICJETlMtRVVXLVhYWC1SRyIsCgkidXNlTWFuYWdlZElkZW
|
||||||
|
50aXR5RXh0ZW5zaW9uIjogdHJ1ZSwKCSJ1c2VyQXNzaWduZWRJZGVudGl0eUlEIjogIlhY
|
||||||
|
WFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIgp9Cg==
|
||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: external-dns
|
app: external-dns
|
||||||
instance: private
|
instance: private
|
||||||
name: azure-config-file-private-%s
|
name: azure-config-file-private-66cc4224mm
|
||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
type: Opaque
|
type: Opaque
|
||||||
`
|
`)
|
||||||
const (
|
|
||||||
nameHashKyaml = "66cc4224mm"
|
|
||||||
contentKyaml = `|
|
|
||||||
ewoJInRlbmFudElkIjogIlhYWFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIiwKCS
|
|
||||||
JzdWJzY3JpcHRpb25JZCI6ICJYWFhYWC1YWFhYWFgtWFhYWFgtWFhYWFhYLVhYWFhYWCIs
|
|
||||||
CgkicmVzb3VyY2VHcm91cCI6ICJETlMtRVVXLVhYWC1SRyIsCgkidXNlTWFuYWdlZElkZW
|
|
||||||
50aXR5RXh0ZW5zaW9uIjogdHJ1ZSwKCSJ1c2VyQXNzaWduZWRJZGVudGl0eUlEIjogIlhY
|
|
||||||
WFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIgp9Cg==`
|
|
||||||
nameHashApiMach = "g2k4bkgt4d"
|
|
||||||
// nolint: lll
|
|
||||||
contentApiMach = `ewoJInRlbmFudElkIjogIlhYWFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIiwKCSJzdWJzY3JpcHRpb25JZCI6ICJYWFhYWC1YWFhYWFgtWFhYWFgtWFhYWFhYLVhYWFhYWCIsCgkicmVzb3VyY2VHcm91cCI6ICJETlMtRVVXLVhYWC1SRyIsCgkidXNlTWFuYWdlZElkZW50aXR5RXh0ZW5zaW9uIjogdHJ1ZSwKCSJ1c2VyQXNzaWduZWRJZGVudGl0eUlEIjogIlhYWFhYLVhYWFhYWC1YWFhYWC1YWFhYWFgtWFhYWFhYIgp9Cg==`
|
|
||||||
)
|
|
||||||
th.AssertActualEqualsExpected(
|
|
||||||
m,
|
|
||||||
// TODO(#3304): DECISION - kyaml better; not a bug.
|
|
||||||
opts.IfApiMachineryElseKyaml(
|
|
||||||
fmt.Sprintf(expFmt,
|
|
||||||
nameHashApiMach,
|
|
||||||
contentApiMach, nameHashApiMach,
|
|
||||||
nameHashApiMach,
|
|
||||||
contentApiMach, nameHashApiMach),
|
|
||||||
fmt.Sprintf(expFmt,
|
|
||||||
nameHashKyaml,
|
|
||||||
contentKyaml, nameHashKyaml,
|
|
||||||
nameHashKyaml,
|
|
||||||
contentKyaml, nameHashKyaml)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyFieldSpecValue(t *testing.T) {
|
func TestEmptyFieldSpecValue(t *testing.T) {
|
||||||
|
|||||||
@@ -306,6 +306,19 @@ kind: CustomResourceDefinition
|
|||||||
metadata:
|
metadata:
|
||||||
name: crds.my.org
|
name: crds.my.org
|
||||||
---
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: namespace.crds.my.org
|
||||||
|
spec:
|
||||||
|
conversion:
|
||||||
|
strategy: Webhook
|
||||||
|
webhook:
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
name: crd-svc
|
||||||
|
namespace: random
|
||||||
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
metadata:
|
metadata:
|
||||||
@@ -407,6 +420,19 @@ kind: CustomResourceDefinition
|
|||||||
metadata:
|
metadata:
|
||||||
name: crds.my.org
|
name: crds.my.org
|
||||||
---
|
---
|
||||||
|
apiVersion: apiextensions.k8s.io/v1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
name: namespace.crds.my.org
|
||||||
|
spec:
|
||||||
|
conversion:
|
||||||
|
strategy: Webhook
|
||||||
|
webhook:
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
name: crd-svc
|
||||||
|
namespace: newnamespace
|
||||||
|
---
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
metadata:
|
metadata:
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package krusty_test
|
package krusty_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -183,6 +186,7 @@ openapi:
|
|||||||
|
|
||||||
func TestCustomOpenApiFieldOverlayTakesPrecedence(t *testing.T) {
|
func TestCustomOpenApiFieldOverlayTakesPrecedence(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
openapi.ResetOpenAPI()
|
||||||
th.WriteK("base", `
|
th.WriteK("base", `
|
||||||
resources:
|
resources:
|
||||||
- mycrd.yaml
|
- mycrd.yaml
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package krusty_test
|
package krusty_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package krusty
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers"
|
"sigs.k8s.io/kustomize/api/internal/plugins/builtinhelpers"
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,37 +32,19 @@ type Options struct {
|
|||||||
|
|
||||||
// Options related to kustomize plugins.
|
// Options related to kustomize plugins.
|
||||||
PluginConfig *types.PluginConfig
|
PluginConfig *types.PluginConfig
|
||||||
|
|
||||||
// TODO(#3588): Delete this field (it's always true).
|
|
||||||
// When true, use kyaml/ packages to manipulate KRM yaml.
|
|
||||||
// When false, use k8sdeps/ instead (uses k8s.io/api* packages).
|
|
||||||
UseKyaml bool
|
|
||||||
|
|
||||||
// When true, allow name and kind changing via a patch
|
|
||||||
// When false, patch name/kind don't overwrite target name/kind
|
|
||||||
AllowResourceIdChanges bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeDefaultOptions returns a default instance of Options.
|
// MakeDefaultOptions returns a default instance of Options.
|
||||||
func MakeDefaultOptions() *Options {
|
func MakeDefaultOptions() *Options {
|
||||||
return &Options{
|
return &Options{
|
||||||
DoLegacyResourceSort: false,
|
DoLegacyResourceSort: false,
|
||||||
AddManagedbyLabel: false,
|
AddManagedbyLabel: false,
|
||||||
LoadRestrictions: types.LoadRestrictionsRootOnly,
|
LoadRestrictions: types.LoadRestrictionsRootOnly,
|
||||||
DoPrune: false,
|
DoPrune: false,
|
||||||
PluginConfig: konfig.DisabledPluginConfig(),
|
PluginConfig: types.DisabledPluginConfig(),
|
||||||
UseKyaml: konfig.FlagEnableKyamlDefaultValue,
|
|
||||||
AllowResourceIdChanges: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Options) IfApiMachineryElseKyaml(s1, s2 string) string {
|
|
||||||
if !o.UseKyaml {
|
|
||||||
return s1
|
|
||||||
}
|
|
||||||
return s2
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBuiltinPluginNames returns a list of builtin plugin names
|
// GetBuiltinPluginNames returns a list of builtin plugin names
|
||||||
func GetBuiltinPluginNames() []string {
|
func GetBuiltinPluginNames() []string {
|
||||||
var ret []string
|
var ret []string
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
package krusty_test
|
package krusty_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
@@ -57,7 +56,6 @@ spec:
|
|||||||
|
|
||||||
func TestPodDisruptionBudgetMerging(t *testing.T) {
|
func TestPodDisruptionBudgetMerging(t *testing.T) {
|
||||||
th := kusttest_test.MakeHarness(t)
|
th := kusttest_test.MakeHarness(t)
|
||||||
opts := th.MakeDefaultOptions()
|
|
||||||
th.WriteF("pdb-patch.yaml", `
|
th.WriteF("pdb-patch.yaml", `
|
||||||
apiVersion: policy/v1beta1
|
apiVersion: policy/v1beta1
|
||||||
kind: PodDisruptionBudget
|
kind: PodDisruptionBudget
|
||||||
@@ -97,26 +95,7 @@ patches:
|
|||||||
resources:
|
resources:
|
||||||
- my_file.yaml
|
- my_file.yaml
|
||||||
`)
|
`)
|
||||||
m := th.Run(".", opts)
|
m := th.Run(".", th.MakeDefaultOptions())
|
||||||
expFmt := `
|
|
||||||
apiVersion: policy/v1beta1
|
|
||||||
kind: PodDisruptionBudget
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
faceit-pdb: default
|
|
||||||
name: championships-api
|
|
||||||
spec:
|
|
||||||
maxUnavailable: %s
|
|
||||||
---
|
|
||||||
apiVersion: policy/v1beta1
|
|
||||||
kind: PodDisruptionBudget
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
faceit-pdb: default
|
|
||||||
name: championships-api-2
|
|
||||||
spec:
|
|
||||||
maxUnavailable: %s
|
|
||||||
`
|
|
||||||
// In a PodDisruptionBudget, the fields maxUnavailable
|
// In a PodDisruptionBudget, the fields maxUnavailable
|
||||||
// minAvailable are mutually exclusive, and both can hold
|
// minAvailable are mutually exclusive, and both can hold
|
||||||
// either an integer, i.e. 10, or string that has to be
|
// either an integer, i.e. 10, or string that has to be
|
||||||
@@ -126,9 +105,23 @@ spec:
|
|||||||
// the percent sign, quotes can be added and the API server will
|
// the percent sign, quotes can be added and the API server will
|
||||||
// accept it, but they don't have to be added.
|
// accept it, but they don't have to be added.
|
||||||
th.AssertActualEqualsExpected(
|
th.AssertActualEqualsExpected(
|
||||||
m,
|
m, `
|
||||||
// TODO(#3304): DECISION - kyaml better; not a bug.
|
apiVersion: policy/v1beta1
|
||||||
opts.IfApiMachineryElseKyaml(
|
kind: PodDisruptionBudget
|
||||||
fmt.Sprintf(expFmt, `"1"`, `"1"`),
|
metadata:
|
||||||
fmt.Sprintf(expFmt, `1`, `1`)))
|
labels:
|
||||||
|
faceit-pdb: default
|
||||||
|
name: championships-api
|
||||||
|
spec:
|
||||||
|
maxUnavailable: 1
|
||||||
|
---
|
||||||
|
apiVersion: policy/v1beta1
|
||||||
|
kind: PodDisruptionBudget
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
faceit-pdb: default
|
||||||
|
name: championships-api-2
|
||||||
|
spec:
|
||||||
|
maxUnavailable: 1
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
|||||||
92454
api/krusty/testdata/customschema.json
vendored
92454
api/krusty/testdata/customschema.json
vendored
File diff suppressed because it is too large
Load Diff
135
api/krusty/validatingwebhook_test.go
Normal file
135
api/krusty/validatingwebhook_test.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package krusty_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reproduce issue #3732
|
||||||
|
func TestValidatingWebhookCombinedNamespaces(t *testing.T) {
|
||||||
|
th := kusttest_test.MakeHarness(t)
|
||||||
|
th.WriteK("base", `
|
||||||
|
resources:
|
||||||
|
- service.yaml
|
||||||
|
- validatingwebhook.yaml
|
||||||
|
`)
|
||||||
|
th.WriteF("base/service.yaml", `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: admission
|
||||||
|
namespace: base-namespace
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- name: https-webhook
|
||||||
|
port: 443
|
||||||
|
targetPort: webhook
|
||||||
|
`)
|
||||||
|
th.WriteF("base/validatingwebhook.yaml", `
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1
|
||||||
|
kind: ValidatingWebhookConfiguration
|
||||||
|
metadata:
|
||||||
|
name: validatingwebhook
|
||||||
|
webhooks:
|
||||||
|
- name: validate
|
||||||
|
matchPolicy: Equivalent
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- networking.k8s.io
|
||||||
|
apiVersions:
|
||||||
|
- v1beta1
|
||||||
|
operations:
|
||||||
|
- CREATE
|
||||||
|
- UPDATE
|
||||||
|
resources:
|
||||||
|
- ingresses
|
||||||
|
failurePolicy: Fail
|
||||||
|
sideEffects: None
|
||||||
|
admissionReviewVersions:
|
||||||
|
- v1
|
||||||
|
- v1beta1
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
namespace: base-namespace
|
||||||
|
name: admission
|
||||||
|
path: /networking/v1beta1/ingresses
|
||||||
|
`)
|
||||||
|
th.WriteK("overlay", `
|
||||||
|
namespace: merge-namespace
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
patchesStrategicMerge:
|
||||||
|
- validatingwebhookdelete.yaml
|
||||||
|
`)
|
||||||
|
th.WriteF("overlay/validatingwebhookdelete.yaml", `
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1
|
||||||
|
kind: ValidatingWebhookConfiguration
|
||||||
|
metadata:
|
||||||
|
name: validatingwebhook
|
||||||
|
$patch: delete
|
||||||
|
`)
|
||||||
|
th.WriteK("combined", `
|
||||||
|
resources:
|
||||||
|
- ../base
|
||||||
|
- ../overlay
|
||||||
|
`)
|
||||||
|
m := th.Run("combined", th.MakeDefaultOptions())
|
||||||
|
th.AssertActualEqualsExpected(m, `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: admission
|
||||||
|
namespace: base-namespace
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: https-webhook
|
||||||
|
port: 443
|
||||||
|
targetPort: webhook
|
||||||
|
type: ClusterIP
|
||||||
|
---
|
||||||
|
apiVersion: admissionregistration.k8s.io/v1
|
||||||
|
kind: ValidatingWebhookConfiguration
|
||||||
|
metadata:
|
||||||
|
name: validatingwebhook
|
||||||
|
webhooks:
|
||||||
|
- admissionReviewVersions:
|
||||||
|
- v1
|
||||||
|
- v1beta1
|
||||||
|
clientConfig:
|
||||||
|
service:
|
||||||
|
name: admission
|
||||||
|
namespace: merge-namespace
|
||||||
|
path: /networking/v1beta1/ingresses
|
||||||
|
failurePolicy: Fail
|
||||||
|
matchPolicy: Equivalent
|
||||||
|
name: validate
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- networking.k8s.io
|
||||||
|
apiVersions:
|
||||||
|
- v1beta1
|
||||||
|
operations:
|
||||||
|
- CREATE
|
||||||
|
- UPDATE
|
||||||
|
resources:
|
||||||
|
- ingresses
|
||||||
|
sideEffects: None
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: admission
|
||||||
|
namespace: merge-namespace
|
||||||
|
spec:
|
||||||
|
ports:
|
||||||
|
- name: https-webhook
|
||||||
|
port: 443
|
||||||
|
targetPort: webhook
|
||||||
|
type: ClusterIP
|
||||||
|
`)
|
||||||
|
}
|
||||||
@@ -319,7 +319,6 @@ func (fl *fileLoader) Load(path string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filepath.IsAbs(path) {
|
if !filepath.IsAbs(path) {
|
||||||
path = fl.root.Join(path)
|
path = fl.root.Join(path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ func MakeFakeFs(td []testData) filesys.FileSystem {
|
|||||||
|
|
||||||
func makeLoader() *fileLoader {
|
func makeLoader() *fileLoader {
|
||||||
return NewFileLoaderAtRoot(MakeFakeFs(testCases))
|
return NewFileLoaderAtRoot(MakeFakeFs(testCases))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoaderLoad(t *testing.T) {
|
func TestLoaderLoad(t *testing.T) {
|
||||||
|
|||||||
@@ -4,198 +4,39 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"sigs.k8s.io/kustomize/api/hasher"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
"sigs.k8s.io/kustomize/api/internal/conflict"
|
|
||||||
"sigs.k8s.io/kustomize/api/internal/validate"
|
"sigs.k8s.io/kustomize/api/internal/validate"
|
||||||
"sigs.k8s.io/kustomize/api/internal/wrappy"
|
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DepProvider is a dependency provider.
|
// DepProvider is a dependency provider, injecting different
|
||||||
//
|
// implementations depending on the context.
|
||||||
// The instances it returns are either
|
|
||||||
// - old implementations backed by k8sdeps code,
|
|
||||||
// - new implementations backed by kyaml code.
|
|
||||||
//
|
|
||||||
// History:
|
|
||||||
//
|
|
||||||
// kubectl depends on k8s.io code, and at the time of writing, so
|
|
||||||
// does kustomize. Code that imports k8s.io/api* cannot be imported
|
|
||||||
// back into k8s.io/*, yet kustomize appears inside k8s.io/kubectl.
|
|
||||||
//
|
|
||||||
// To allow kustomize to appear inside kubectl, yet still be developed
|
|
||||||
// outside kubectl, the kustomize code was divided into the following
|
|
||||||
// packages
|
|
||||||
//
|
|
||||||
// api/
|
|
||||||
// k8sdeps/ (and internal/ks8deps/)
|
|
||||||
// ifc/
|
|
||||||
// krusty/
|
|
||||||
// everythingElse/
|
|
||||||
//
|
|
||||||
// with the following rules:
|
|
||||||
//
|
|
||||||
// - Only k8sdeps/ may import k8s.io/api*.
|
|
||||||
//
|
|
||||||
// - Only krusty/ (and its internals) may import k8sdeps/.
|
|
||||||
// I.e., ifc/ and everythingElse/ must not
|
|
||||||
// import k8sdeps/ or k8s.io/api*.
|
|
||||||
//
|
|
||||||
// - Code in krusty/ may use code in k8sdeps/ to create
|
|
||||||
// objects then inject said objects into
|
|
||||||
// everythingElse/ behind dependency neutral interfaces.
|
|
||||||
//
|
|
||||||
// The idea was to periodically copy, not import, the large k8sdeps/
|
|
||||||
// tree (plus a snippet from krusty/kustomizer.go) into the kubectl
|
|
||||||
// codebase via a large PR, and have kubectl depend on the rest via
|
|
||||||
// normal importing.
|
|
||||||
//
|
|
||||||
// Over 2019, however, kubectl underwent large changes including
|
|
||||||
// a switch to Go modules, and a concerted attempt to extract kubectl
|
|
||||||
// from the k8s repo. This made large kustomize integration PRs too
|
|
||||||
// intrusive to review.
|
|
||||||
//
|
|
||||||
// In 2020, kubectl is based on Go modules, and almost entirely
|
|
||||||
// extracted from the k8s.io repositories, and further the kyaml
|
|
||||||
// library has a appeared as a viable replacement to k8s.io/api*
|
|
||||||
// KRM manipulation code.
|
|
||||||
//
|
|
||||||
// The new plan is to eliminate k8sdeps/ entirely, along with its
|
|
||||||
// k8s.io/api* dependence, allowing kustomize code to be imported
|
|
||||||
// into kubectl via normal Go module imports. Then the kustomize API
|
|
||||||
// code can then move into the github.com/kubernetes-sigs/cli-utils
|
|
||||||
// repo. The kustomize CLI in github.com/kubernetes-sigs/kustomize
|
|
||||||
// and the kubectl CLI can then both depend on the kustomize API.
|
|
||||||
//
|
|
||||||
// So, all code that depends on k8sdeps must go behind interfaces,
|
|
||||||
// and kustomize must be factored to choose the implementation.
|
|
||||||
//
|
|
||||||
// That problem has been reduced to three interfaces, each having
|
|
||||||
// two implementations. (1) is k8sdeps-based, (2) is kyaml-based.
|
|
||||||
//
|
|
||||||
// - ifc.Kunstructured
|
|
||||||
//
|
|
||||||
// 1) api/k8sdeps/kunstruct.UnstructAdapter
|
|
||||||
//
|
|
||||||
// This adapts structs in
|
|
||||||
// k8s.io/apimachinery/pkg/apis/meta/v1/unstructured
|
|
||||||
// to ifc.Kunstructured.
|
|
||||||
//
|
|
||||||
// 2) api/wrappy.WNode
|
|
||||||
//
|
|
||||||
// This adapts sigs.k8s.io/kustomize/kyaml/yaml.RNode
|
|
||||||
// to ifc.Unstructured.
|
|
||||||
//
|
|
||||||
// At time of writing, implementation started.
|
|
||||||
// Further reducing the size of ifc.Kunstructed
|
|
||||||
// would really reduce the work
|
|
||||||
// (e.g. drop Vars, drop ReplacementTranformer).
|
|
||||||
//
|
|
||||||
// - resource.ConflictDetector
|
|
||||||
//
|
|
||||||
// 1) api/internal/k8sdeps/conflict.conflictDetectorJson
|
|
||||||
// api/internal/k8sdeps/conflict.conflictDetectorSm
|
|
||||||
//
|
|
||||||
// Uses k8s.io/apimachinery/pkg/util/strategicpatch,
|
|
||||||
// apimachinery/pkg/util/mergepatch, etc. to merge
|
|
||||||
// resource.Resource instances.
|
|
||||||
//
|
|
||||||
// 2) api/internal/conflict.smPatchMergeOnlyDetector
|
|
||||||
//
|
|
||||||
// At time of writing, this doesn't report conflicts,
|
|
||||||
// but it does know how to merge patches. Conflict
|
|
||||||
// reporting isn't vital to kustomize function. It's
|
|
||||||
// rare that a person would configure one transformer
|
|
||||||
// with many patches, much less so many that it became
|
|
||||||
// hard to spot conflicts. In the case of an undetected
|
|
||||||
// conflict, the last patch applied wins, likely what
|
|
||||||
// the user wants anyway. Regardless, the effect of this
|
|
||||||
// is plainly visible and usable in the output, even if
|
|
||||||
// a conflict happened but wasn't reported as an error.
|
|
||||||
//
|
|
||||||
// - ifc.Validator
|
|
||||||
//
|
|
||||||
// 1) api/k8sdeps/validator.KustValidator
|
|
||||||
//
|
|
||||||
// Uses k8s.io/apimachinery/pkg/api/validation and
|
|
||||||
// friends to validate strings.
|
|
||||||
//
|
|
||||||
// 2) api/internal/validate.FieldValidator
|
|
||||||
//
|
|
||||||
// See TODO inside the validator for status.
|
|
||||||
// At time of writing, this is a do-nothing
|
|
||||||
// validator as it's not critical to kustomize function.
|
|
||||||
//
|
|
||||||
// Proposed plan:
|
|
||||||
// [x] Ship kustomize with the ability to switch from 1 to 2 via
|
|
||||||
// an --enable_kyaml flag.
|
|
||||||
// [x] Make --enable_kyaml true by default.
|
|
||||||
// [x] When 2 is not noticeably more buggy than 1, delete 1.
|
|
||||||
// I.e. delete k8sdeps/, transitively deleting all k8s.io/api* deps.
|
|
||||||
// This DepProvider should be left in place to retain these
|
|
||||||
// comments, but it will have only one choice.
|
|
||||||
// [x] The way is now clear to reintegrate into kubectl.
|
|
||||||
// This should be done ASAP; the last step is cleanup.
|
|
||||||
// [ ] Cleanup. With only one impl of Kunstructure remaining,
|
|
||||||
// that interface and WNode can be deleted, along with this
|
|
||||||
// DepProvider. The other two interfaces could be dropped too.
|
|
||||||
//
|
|
||||||
// When the above is done, kustomize will use yaml.RNode and/or
|
|
||||||
// KRM Config Functions directly and exclusively.
|
|
||||||
// If you're reading this, plan not done.
|
|
||||||
//
|
|
||||||
type DepProvider struct {
|
type DepProvider struct {
|
||||||
kFactory ifc.KunstructuredFactory
|
resourceFactory *resource.Factory
|
||||||
resourceFactory *resource.Factory
|
// implemented by api/internal/validate.FieldValidator
|
||||||
conflictDectectorFactory resource.ConflictDetectorFactory
|
// See TODO inside the validator for status.
|
||||||
fieldValidator ifc.Validator
|
// At time of writing, this is a do-nothing
|
||||||
|
// validator as it's not critical to kustomize function.
|
||||||
|
fieldValidator ifc.Validator
|
||||||
}
|
}
|
||||||
|
|
||||||
// The dependencies this method needs have been deleted -
|
func NewDepProvider() *DepProvider {
|
||||||
// see comments above. This method will be deleted
|
rf := resource.NewFactory(&hasher.Hasher{})
|
||||||
// along with DepProvider in the final step.
|
|
||||||
func makeK8sdepBasedInstances() *DepProvider {
|
|
||||||
log.Fatal("This binary cannot use k8s.io code; it must use kyaml.")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeKyamlBasedInstances() *DepProvider {
|
|
||||||
kf := &wrappy.WNodeFactory{}
|
|
||||||
rf := resource.NewFactory(kf)
|
|
||||||
return &DepProvider{
|
return &DepProvider{
|
||||||
kFactory: kf,
|
resourceFactory: rf,
|
||||||
resourceFactory: rf,
|
fieldValidator: validate.NewFieldValidator(),
|
||||||
conflictDectectorFactory: conflict.NewFactory(),
|
|
||||||
fieldValidator: validate.NewFieldValidator(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDepProvider(useKyaml bool) *DepProvider {
|
|
||||||
if useKyaml {
|
|
||||||
return makeKyamlBasedInstances()
|
|
||||||
}
|
|
||||||
return makeK8sdepBasedInstances()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDefaultDepProvider() *DepProvider {
|
func NewDefaultDepProvider() *DepProvider {
|
||||||
return NewDepProvider(konfig.FlagEnableKyamlDefaultValue)
|
return NewDepProvider()
|
||||||
}
|
|
||||||
|
|
||||||
func (dp *DepProvider) GetKunstructuredFactory() ifc.KunstructuredFactory {
|
|
||||||
return dp.kFactory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dp *DepProvider) GetResourceFactory() *resource.Factory {
|
func (dp *DepProvider) GetResourceFactory() *resource.Factory {
|
||||||
return dp.resourceFactory
|
return dp.resourceFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dp *DepProvider) GetConflictDetectorFactory() resource.ConflictDetectorFactory {
|
|
||||||
return dp.conflictDectectorFactory
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dp *DepProvider) GetFieldValidator() ifc.Validator {
|
func (dp *DepProvider) GetFieldValidator() ifc.Validator {
|
||||||
return dp.fieldValidator
|
return dp.fieldValidator
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,14 @@ func ParseGroupVersion(apiVersion string) (group, version string) {
|
|||||||
// GvkFromString makes a Gvk from the output of Gvk.String().
|
// GvkFromString makes a Gvk from the output of Gvk.String().
|
||||||
func GvkFromString(s string) Gvk {
|
func GvkFromString(s string) Gvk {
|
||||||
values := strings.Split(s, fieldSep)
|
values := strings.Split(s, fieldSep)
|
||||||
|
if len(values) != 3 {
|
||||||
|
// ...then the string didn't come from Gvk.String().
|
||||||
|
return Gvk{
|
||||||
|
Group: noGroup,
|
||||||
|
Version: noVersion,
|
||||||
|
Kind: noKind,
|
||||||
|
}
|
||||||
|
}
|
||||||
g := values[0]
|
g := values[0]
|
||||||
if g == noGroup {
|
if g == noGroup {
|
||||||
g = ""
|
g = ""
|
||||||
@@ -213,7 +221,10 @@ func (x Gvk) toKyamlTypeMeta() yaml.TypeMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsNamespaceableKind returns true if x is a namespaceable Gvk
|
// IsNamespaceableKind returns true if x is a namespaceable Gvk,
|
||||||
|
// e.g. instances of Pod and Deployment are namespaceable,
|
||||||
|
// but instances of Node and Namespace are not namespaceable.
|
||||||
|
// Alternative name for this method: IsNotClusterScoped.
|
||||||
// Implements https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#not-all-objects-are-in-a-namespace
|
// Implements https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#not-all-objects-are-in-a-namespace
|
||||||
func (x Gvk) IsNamespaceableKind() bool {
|
func (x Gvk) IsNamespaceableKind() bool {
|
||||||
isNamespaceScoped, found := openapi.IsNamespaceScoped(x.toKyamlTypeMeta())
|
isNamespaceScoped, found := openapi.IsNamespaceScoped(x.toKyamlTypeMeta())
|
||||||
|
|||||||
@@ -103,6 +103,12 @@ func TestString(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGvkFromString(t *testing.T) {
|
||||||
|
for _, hey := range stringTests {
|
||||||
|
assert.Equal(t, hey.x, GvkFromString(hey.s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApiVersion(t *testing.T) {
|
func TestApiVersion(t *testing.T) {
|
||||||
for _, hey := range []struct {
|
for _, hey := range []struct {
|
||||||
x Gvk
|
x Gvk
|
||||||
|
|||||||
@@ -16,14 +16,11 @@ import (
|
|||||||
type Factory struct {
|
type Factory struct {
|
||||||
// Makes resources.
|
// Makes resources.
|
||||||
resF *resource.Factory
|
resF *resource.Factory
|
||||||
// Makes ConflictDetectors.
|
|
||||||
cdf resource.ConflictDetectorFactory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFactory returns a new resmap.Factory.
|
// NewFactory returns a new resmap.Factory.
|
||||||
func NewFactory(
|
func NewFactory(rf *resource.Factory) *Factory {
|
||||||
rf *resource.Factory, cdf resource.ConflictDetectorFactory) *Factory {
|
return &Factory{resF: rf}
|
||||||
return &Factory{resF: rf, cdf: cdf}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RF returns a resource.Factory.
|
// RF returns a resource.Factory.
|
||||||
@@ -126,13 +123,6 @@ func (rmF *Factory) FromSecretArgs(
|
|||||||
return rmF.FromResource(res), nil
|
return rmF.FromResource(res), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConflatePatches creates a new ResMap containing a merger of the
|
|
||||||
// incoming patches.
|
|
||||||
// Error if conflict found.
|
|
||||||
func (rmF *Factory) ConflatePatches(patches []*resource.Resource) (ResMap, error) {
|
|
||||||
return (&merginator{cdf: rmF.cdf}).ConflatePatches(patches)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newResMapFromResourceSlice(
|
func newResMapFromResourceSlice(
|
||||||
resources []*resource.Resource) (ResMap, error) {
|
resources []*resource.Resource) (ResMap, error) {
|
||||||
result := New()
|
result := New()
|
||||||
@@ -146,18 +136,10 @@ func newResMapFromResourceSlice(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewResMapFromRNodeSlice returns a ResMap from a slice of RNodes
|
// NewResMapFromRNodeSlice returns a ResMap from a slice of RNodes
|
||||||
func (rmF *Factory) NewResMapFromRNodeSlice(rnodes []*yaml.RNode) (ResMap, error) {
|
func (rmF *Factory) NewResMapFromRNodeSlice(s []*yaml.RNode) (ResMap, error) {
|
||||||
var resources []*resource.Resource
|
rs, err := rmF.resF.ResourcesFromRNodes(s)
|
||||||
for _, rnode := range rnodes {
|
if err != nil {
|
||||||
s, err := rnode.String()
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r, err := rmF.resF.SliceFromBytes([]byte(s))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resources = append(resources, r...)
|
|
||||||
}
|
}
|
||||||
return newResMapFromResourceSlice(resources)
|
return newResMapFromResourceSlice(rs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,9 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
"sigs.k8s.io/kustomize/api/kv"
|
"sigs.k8s.io/kustomize/api/kv"
|
||||||
"sigs.k8s.io/kustomize/api/loader"
|
"sigs.k8s.io/kustomize/api/loader"
|
||||||
. "sigs.k8s.io/kustomize/api/resmap"
|
. "sigs.k8s.io/kustomize/api/resmap"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||||
valtest_test "sigs.k8s.io/kustomize/api/testutils/valtest"
|
valtest_test "sigs.k8s.io/kustomize/api/testutils/valtest"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
@@ -337,82 +335,17 @@ metadata:
|
|||||||
expected: resmaptest_test.NewRmBuilder(t, rf).ResMap(),
|
expected: resmaptest_test.NewRmBuilder(t, rf).ResMap(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for name, tc := range testcases {
|
for name := range testcases {
|
||||||
rnodes := []*yaml.RNode{
|
tc := testcases[name]
|
||||||
yaml.MustParse(tc.input),
|
t.Run(name, func(t *testing.T) {
|
||||||
}
|
rm, err := rmF.NewResMapFromRNodeSlice(
|
||||||
rm, err := rmF.NewResMapFromRNodeSlice(rnodes)
|
[]*yaml.RNode{yaml.MustParse(tc.input)})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error in test case [%s]: %v", name, err)
|
t.Fatalf("unexpected error in test case [%s]: %v", name, err)
|
||||||
}
|
}
|
||||||
if err = tc.expected.ErrorIfNotEqualLists(rm); err != nil {
|
if err = tc.expected.ErrorIfNotEqualLists(rm); err != nil {
|
||||||
t.Fatalf("error in test case [%s]: %s", name, err)
|
t.Fatalf("error in test case [%s]: %s", name, err)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConflatePatches_Empty(t *testing.T) {
|
|
||||||
rm, err := rmF.ConflatePatches([]*resource.Resource{})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, 0, rm.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConflatePatches(t *testing.T) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
yml []byte
|
|
||||||
r1, r2 *resource.Resource
|
|
||||||
)
|
|
||||||
r1, err = rf.FromBytes([]byte(`apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: my-foo
|
|
||||||
spec:
|
|
||||||
bar:
|
|
||||||
B:
|
|
||||||
C: Z
|
|
||||||
`))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
r2, err = rf.FromBytes([]byte(`apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: my-foo
|
|
||||||
spec:
|
|
||||||
bar:
|
|
||||||
C: Z
|
|
||||||
D: W
|
|
||||||
baz:
|
|
||||||
hello: world
|
|
||||||
`))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
rm, err := rmF.ConflatePatches([]*resource.Resource{r1, r2})
|
|
||||||
assert.NoError(t, err)
|
|
||||||
yml, err = rm.AsYaml()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
// TODO(#3304): DECISION - kyaml better; not a bug.
|
|
||||||
assert.Equal(t, konfig.IfApiMachineryElseKyaml(`apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: my-foo
|
|
||||||
spec:
|
|
||||||
bar:
|
|
||||||
B: null
|
|
||||||
C: Z
|
|
||||||
D: W
|
|
||||||
baz:
|
|
||||||
hello: world
|
|
||||||
`, `apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: my-foo
|
|
||||||
spec:
|
|
||||||
bar:
|
|
||||||
C: Z
|
|
||||||
D: W
|
|
||||||
baz:
|
|
||||||
hello: world
|
|
||||||
`), string(yml))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package resmap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
)
|
|
||||||
|
|
||||||
// merginator coordinates merging the resources in incoming to the result.
|
|
||||||
type merginator struct {
|
|
||||||
incoming []*resource.Resource
|
|
||||||
cdf resource.ConflictDetectorFactory
|
|
||||||
result ResMap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *merginator) ConflatePatches(in []*resource.Resource) (ResMap, error) {
|
|
||||||
m.result = New()
|
|
||||||
m.incoming = in
|
|
||||||
for index := range m.incoming {
|
|
||||||
alreadyInResult, err := m.appendIfNoMatch(index)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if alreadyInResult != nil {
|
|
||||||
// The resource at index has the same resId as a previously
|
|
||||||
// considered resource.
|
|
||||||
//
|
|
||||||
// If they conflict with each other (e.g. they both want to change
|
|
||||||
// the image name in a Deployment, but to different values),
|
|
||||||
// return an error.
|
|
||||||
//
|
|
||||||
// If they don't conflict, then merge them into a single resource,
|
|
||||||
// since they both target the same item, and we want cumulative
|
|
||||||
// behavior. E.g. say both patches modify a map. Without a merge,
|
|
||||||
// the last patch wins, replacing the entire map.
|
|
||||||
err = m.mergeWithExisting(index, alreadyInResult)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m.result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *merginator) appendIfNoMatch(index int) (*resource.Resource, error) {
|
|
||||||
candidate := m.incoming[index]
|
|
||||||
matchedResources := m.result.GetMatchingResourcesByAnyId(
|
|
||||||
candidate.OrgId().Equals)
|
|
||||||
if len(matchedResources) == 0 {
|
|
||||||
m.result.Append(candidate)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if len(matchedResources) > 1 {
|
|
||||||
return nil, fmt.Errorf("multiple resources targeted by patch")
|
|
||||||
}
|
|
||||||
return matchedResources[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *merginator) mergeWithExisting(
|
|
||||||
index int, alreadyInResult *resource.Resource) error {
|
|
||||||
candidate := m.incoming[index]
|
|
||||||
cd, err := m.cdf.New(candidate.OrgId().Gvk)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
hasConflict, err := cd.HasConflict(candidate, alreadyInResult)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if hasConflict {
|
|
||||||
return m.makeError(cd, index)
|
|
||||||
}
|
|
||||||
merged, err := cd.MergePatches(alreadyInResult, candidate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = m.result.Replace(merged)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make an error message describing the conflict.
|
|
||||||
func (m *merginator) makeError(cd resource.ConflictDetector, index int) error {
|
|
||||||
conflict, err := m.findConflict(cd, index)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if conflict == nil {
|
|
||||||
return fmt.Errorf("expected conflict for %s", m.incoming[index].OrgId())
|
|
||||||
}
|
|
||||||
conflictMap, _ := conflict.Map()
|
|
||||||
incomingIndexMap, _ := m.incoming[index].Map()
|
|
||||||
return fmt.Errorf(
|
|
||||||
"conflict between %#v at index %d and %#v",
|
|
||||||
incomingIndexMap,
|
|
||||||
index,
|
|
||||||
conflictMap,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// findConflict looks for a conflict in a resource slice.
|
|
||||||
// It returns the first conflict between the resource at index
|
|
||||||
// and some other resource. Two resources can only conflict if
|
|
||||||
// they have the same original ResId.
|
|
||||||
func (m *merginator) findConflict(
|
|
||||||
cd resource.ConflictDetector, index int) (*resource.Resource, error) {
|
|
||||||
targetId := m.incoming[index].OrgId()
|
|
||||||
for i, p := range m.incoming {
|
|
||||||
if i == index || !targetId.Equals(p.OrgId()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
conflict, err := cd.HasConflict(p, m.incoming[index])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if conflict {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
@@ -33,8 +33,10 @@ type Configurable interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPluginHelpers makes an instance of PluginHelpers.
|
// NewPluginHelpers makes an instance of PluginHelpers.
|
||||||
func NewPluginHelpers(ldr ifc.Loader, v ifc.Validator, rf *Factory) *PluginHelpers {
|
func NewPluginHelpers(
|
||||||
return &PluginHelpers{ldr: ldr, v: v, rf: rf}
|
ldr ifc.Loader, v ifc.Validator, rf *Factory,
|
||||||
|
pc *types.PluginConfig) *PluginHelpers {
|
||||||
|
return &PluginHelpers{ldr: ldr, v: v, rf: rf, pc: pc}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginHelpers holds things that any or all plugins might need.
|
// PluginHelpers holds things that any or all plugins might need.
|
||||||
@@ -44,6 +46,11 @@ type PluginHelpers struct {
|
|||||||
ldr ifc.Loader
|
ldr ifc.Loader
|
||||||
v ifc.Validator
|
v ifc.Validator
|
||||||
rf *Factory
|
rf *Factory
|
||||||
|
pc *types.PluginConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *PluginHelpers) GeneralConfig() *types.PluginConfig {
|
||||||
|
return c.pc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *PluginHelpers) Loader() ifc.Loader {
|
func (c *PluginHelpers) Loader() ifc.Loader {
|
||||||
@@ -80,6 +87,9 @@ type TransformerPlugin interface {
|
|||||||
// resource to transform, try the OrgId first, and if this
|
// resource to transform, try the OrgId first, and if this
|
||||||
// fails or finds too many, it might make sense to then try
|
// fails or finds too many, it might make sense to then try
|
||||||
// the CurrId. Depends on the situation.
|
// the CurrId. Depends on the situation.
|
||||||
|
//
|
||||||
|
// TODO: get rid of this interface (use bare resWrangler).
|
||||||
|
// There aren't multiple implementations any more.
|
||||||
type ResMap interface {
|
type ResMap interface {
|
||||||
// Size reports the number of resources.
|
// Size reports the number of resources.
|
||||||
Size() int
|
Size() int
|
||||||
@@ -189,6 +199,9 @@ type ResMap interface {
|
|||||||
// Clear removes all resources and Ids.
|
// Clear removes all resources and Ids.
|
||||||
Clear()
|
Clear()
|
||||||
|
|
||||||
|
// DropEmpties drops empty resources from the ResMap.
|
||||||
|
DropEmpties()
|
||||||
|
|
||||||
// SubsetThatCouldBeReferencedByResource returns a ResMap subset
|
// SubsetThatCouldBeReferencedByResource returns a ResMap subset
|
||||||
// of self with resources that could be referenced by the
|
// of self with resources that could be referenced by the
|
||||||
// resource argument.
|
// resource argument.
|
||||||
@@ -231,9 +244,8 @@ type ResMap interface {
|
|||||||
// are selected by a Selector
|
// are selected by a Selector
|
||||||
Select(types.Selector) ([]*resource.Resource, error)
|
Select(types.Selector) ([]*resource.Resource, error)
|
||||||
|
|
||||||
// ToRNodeSlice converts the resources in the resmp
|
// ToRNodeSlice returns a copy of the resources as RNodes.
|
||||||
// to a list of RNodes
|
ToRNodeSlice() []*yaml.RNode
|
||||||
ToRNodeSlice() ([]*yaml.RNode, error)
|
|
||||||
|
|
||||||
// ApplySmPatch applies a strategic-merge patch to the
|
// ApplySmPatch applies a strategic-merge patch to the
|
||||||
// selected set of resources.
|
// selected set of resources.
|
||||||
|
|||||||
@@ -6,14 +6,12 @@ package resmap
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
kyaml_yaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// resWrangler implements ResMap.
|
// resWrangler implements ResMap.
|
||||||
@@ -38,6 +36,18 @@ func (m *resWrangler) Clear() {
|
|||||||
m.rList = nil
|
m.rList = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DropEmpties quickly drops empty resources.
|
||||||
|
// It doesn't use Append, which checks for Id collisions.
|
||||||
|
func (m *resWrangler) DropEmpties() {
|
||||||
|
var rList []*resource.Resource
|
||||||
|
for _, r := range m.rList {
|
||||||
|
if !r.IsEmpty() {
|
||||||
|
rList = append(rList, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.rList = rList
|
||||||
|
}
|
||||||
|
|
||||||
// Size implements ResMap.
|
// Size implements ResMap.
|
||||||
func (m *resWrangler) Size() int {
|
func (m *resWrangler) Size() int {
|
||||||
return len(m.rList)
|
return len(m.rList)
|
||||||
@@ -66,22 +76,27 @@ func (m *resWrangler) Append(res *resource.Resource) error {
|
|||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"may not add resource with an already registered id: %s", id)
|
"may not add resource with an already registered id: %s", id)
|
||||||
}
|
}
|
||||||
m.rList = append(m.rList, res)
|
m.append(res)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// append appends without performing an Id check
|
||||||
|
func (m *resWrangler) append(res *resource.Resource) {
|
||||||
|
m.rList = append(m.rList, res)
|
||||||
|
}
|
||||||
|
|
||||||
// Remove implements ResMap.
|
// Remove implements ResMap.
|
||||||
func (m *resWrangler) Remove(adios resid.ResId) error {
|
func (m *resWrangler) Remove(adios resid.ResId) error {
|
||||||
tmp := newOne()
|
var rList []*resource.Resource
|
||||||
for _, r := range m.rList {
|
for _, r := range m.rList {
|
||||||
if r.CurId() != adios {
|
if r.CurId() != adios {
|
||||||
tmp.Append(r)
|
rList = append(rList, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tmp.Size() != m.Size()-1 {
|
if len(rList) != m.Size()-1 {
|
||||||
return fmt.Errorf("id %s not found in removal", adios)
|
return fmt.Errorf("id %s not found in removal", adios)
|
||||||
}
|
}
|
||||||
m.rList = tmp.rList
|
m.rList = rList
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,16 +133,7 @@ func (m *resWrangler) Debug(title string) {
|
|||||||
} else {
|
} else {
|
||||||
fmt.Println("---")
|
fmt.Println("---")
|
||||||
}
|
}
|
||||||
fmt.Printf("# %d %s\n", i, r.OrgId())
|
fmt.Printf("# %d %s\n%s\n", i, r.OrgId(), r.String())
|
||||||
m, err := r.Map()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
blob, err := yaml.Marshal(m)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(blob))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +279,7 @@ func (m *resWrangler) AsYaml() ([]byte, error) {
|
|||||||
firstObj := true
|
firstObj := true
|
||||||
var b []byte
|
var b []byte
|
||||||
buf := bytes.NewBuffer(b)
|
buf := bytes.NewBuffer(b)
|
||||||
for _, res := range m.Resources() {
|
for _, res := range m.rList {
|
||||||
out, err := res.AsYAML()
|
out, err := res.AsYAML()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m, _ := res.Map()
|
m, _ := res.Map()
|
||||||
@@ -297,7 +303,7 @@ func (m *resWrangler) AsYaml() ([]byte, error) {
|
|||||||
func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
||||||
m2, ok := other.(*resWrangler)
|
m2, ok := other.(*resWrangler)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("bad cast")
|
return fmt.Errorf("bad cast to resWrangler 1")
|
||||||
}
|
}
|
||||||
if m.Size() != m2.Size() {
|
if m.Size() != m2.Size() {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
@@ -317,9 +323,9 @@ func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
|||||||
"id in self matches %d in other; id: %s", len(others), id)
|
"id in self matches %d in other; id: %s", len(others), id)
|
||||||
}
|
}
|
||||||
r2 := others[0]
|
r2 := others[0]
|
||||||
if !r1.KunstructEqual(r2) {
|
if !r1.NodeEqual(r2) {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"kunstruct not equal: \n -- %s,\n -- %s\n\n--\n%#v\n------\n%#v\n",
|
"nodes unequal: \n -- %s,\n -- %s\n\n--\n%#v\n------\n%#v\n",
|
||||||
r1, r2, r1, r2)
|
r1, r2, r1, r2)
|
||||||
}
|
}
|
||||||
seen[m2.indexOfResource(r2)] = true
|
seen[m2.indexOfResource(r2)] = true
|
||||||
@@ -334,7 +340,7 @@ func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
|||||||
func (m *resWrangler) ErrorIfNotEqualLists(other ResMap) error {
|
func (m *resWrangler) ErrorIfNotEqualLists(other ResMap) error {
|
||||||
m2, ok := other.(*resWrangler)
|
m2, ok := other.(*resWrangler)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("bad cast")
|
return fmt.Errorf("bad cast to resWrangler 2")
|
||||||
}
|
}
|
||||||
if m.Size() != m2.Size() {
|
if m.Size() != m2.Size() {
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
@@ -388,7 +394,7 @@ func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
|||||||
}
|
}
|
||||||
result := newOne()
|
result := newOne()
|
||||||
roleBindingNamespaces := getNamespacesForRoleBinding(referrer)
|
roleBindingNamespaces := getNamespacesForRoleBinding(referrer)
|
||||||
for _, possibleTarget := range m.Resources() {
|
for _, possibleTarget := range m.rList {
|
||||||
id := possibleTarget.CurId()
|
id := possibleTarget.CurId()
|
||||||
if !id.IsNamespaceableKind() {
|
if !id.IsNamespaceableKind() {
|
||||||
// A cluster-scoped resource can be referred to by anything.
|
// A cluster-scoped resource can be referred to by anything.
|
||||||
@@ -435,16 +441,21 @@ func getNamespacesForRoleBinding(r *resource.Resource) map[string]bool {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *resWrangler) append(res *resource.Resource) {
|
|
||||||
m.rList = append(m.rList, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendAll implements ResMap.
|
// AppendAll implements ResMap.
|
||||||
func (m *resWrangler) AppendAll(other ResMap) error {
|
func (m *resWrangler) AppendAll(other ResMap) error {
|
||||||
if other == nil {
|
if other == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, res := range other.Resources() {
|
m2, ok := other.(*resWrangler)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("bad cast to resWrangler 3")
|
||||||
|
}
|
||||||
|
return m.appendAll(m2.rList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendAll appends all the resources, error on Id collision.
|
||||||
|
func (m *resWrangler) appendAll(list []*resource.Resource) error {
|
||||||
|
for _, res := range list {
|
||||||
if err := m.Append(res); err != nil {
|
if err := m.Append(res); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -457,7 +468,11 @@ func (m *resWrangler) AbsorbAll(other ResMap) error {
|
|||||||
if other == nil {
|
if other == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for _, r := range other.Resources() {
|
m2, ok := other.(*resWrangler)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("bad cast to resWrangler 4")
|
||||||
|
}
|
||||||
|
for _, r := range m2.rList {
|
||||||
err := m.appendReplaceOrMerge(r)
|
err := m.appendReplaceOrMerge(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -522,7 +537,7 @@ func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.rList {
|
||||||
curId := r.CurId()
|
curId := r.CurId()
|
||||||
orgId := r.OrgId()
|
orgId := r.OrgId()
|
||||||
|
|
||||||
@@ -567,77 +582,39 @@ func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToRNodeSlice converts the resources in the resmp
|
// ToRNodeSlice returns a copy of the resources as RNodes.
|
||||||
// to a list of RNodes
|
func (m *resWrangler) ToRNodeSlice() []*kyaml.RNode {
|
||||||
func (m *resWrangler) ToRNodeSlice() ([]*kyaml_yaml.RNode, error) {
|
result := make([]*kyaml.RNode, len(m.rList))
|
||||||
var rnodes []*kyaml_yaml.RNode
|
for i := range m.rList {
|
||||||
for _, r := range m.Resources() {
|
result[i] = m.rList[i].AsRNode()
|
||||||
s, err := r.AsYAML()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rnode, err := kyaml_yaml.Parse(string(s))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rnodes = append(rnodes, rnode)
|
|
||||||
}
|
}
|
||||||
return rnodes, nil
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApplySmPatch applies the patch, and errors on Id collisions.
|
||||||
func (m *resWrangler) ApplySmPatch(
|
func (m *resWrangler) ApplySmPatch(
|
||||||
selectedSet *resource.IdSet, patch *resource.Resource) error {
|
selectedSet *resource.IdSet, patch *resource.Resource) error {
|
||||||
newRm := New()
|
var list []*resource.Resource
|
||||||
for _, res := range m.Resources() {
|
for _, res := range m.rList {
|
||||||
if !selectedSet.Contains(res.CurId()) {
|
if selectedSet.Contains(res.CurId()) {
|
||||||
newRm.Append(res)
|
patchCopy := patch.DeepCopy()
|
||||||
continue
|
patchCopy.CopyMergeMetaDataFieldsFrom(patch)
|
||||||
}
|
patchCopy.SetGvk(res.GetGvk())
|
||||||
patchCopy := patch.DeepCopy()
|
patchCopy.SetKind(patch.GetKind())
|
||||||
patchCopy.CopyMergeMetaDataFieldsFrom(patch)
|
if err := res.ApplySmPatch(patchCopy); err != nil {
|
||||||
patchCopy.SetGvk(res.GetGvk())
|
|
||||||
err := res.ApplySmPatch(patchCopy)
|
|
||||||
if err != nil {
|
|
||||||
// Check for an error string from UnmarshalJSON that's indicative
|
|
||||||
// of an object that's missing basic KRM fields, and thus may have been
|
|
||||||
// entirely deleted (an acceptable outcome). This error handling should
|
|
||||||
// be deleted along with use of ResMap and apimachinery functions like
|
|
||||||
// UnmarshalJSON.
|
|
||||||
if !strings.Contains(err.Error(), "Object 'Kind' is missing") {
|
|
||||||
// Some unknown error, let it through.
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
empty, err := res.IsEmpty()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !empty {
|
|
||||||
m, _ := res.Map()
|
|
||||||
return errors.Wrapf(
|
|
||||||
err, "with unexpectedly non-empty object map of size %d",
|
|
||||||
len(m))
|
|
||||||
}
|
|
||||||
// Fall through to handle deleted object.
|
|
||||||
}
|
}
|
||||||
empty, err := res.IsEmpty()
|
if !res.IsEmpty() {
|
||||||
if err != nil {
|
list = append(list, res)
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !empty {
|
|
||||||
// IsEmpty means all fields have been removed from the object.
|
|
||||||
// This can happen if a patch required deletion of the
|
|
||||||
// entire resource (not just a part of it). This means
|
|
||||||
// the overall resmap must shrink by one.
|
|
||||||
newRm.Append(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.Clear()
|
m.Clear()
|
||||||
m.AppendAll(newRm)
|
return m.appendAll(list)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *resWrangler) RemoveBuildAnnotations() {
|
func (m *resWrangler) RemoveBuildAnnotations() {
|
||||||
for _, r := range m.Resources() {
|
for _, r := range m.rList {
|
||||||
r.RemoveBuildAnnotations()
|
r.RemoveBuildAnnotations()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
|
|
||||||
var depProvider = provider.NewDefaultDepProvider()
|
var depProvider = provider.NewDefaultDepProvider()
|
||||||
var rf = depProvider.GetResourceFactory()
|
var rf = depProvider.GetResourceFactory()
|
||||||
var rmF = NewFactory(rf, depProvider.GetConflictDetectorFactory())
|
var rmF = NewFactory(rf)
|
||||||
|
|
||||||
func doAppend(t *testing.T, w ResMap, r *resource.Resource) {
|
func doAppend(t *testing.T, w ResMap, r *resource.Resource) {
|
||||||
err := w.Append(r)
|
err := w.Append(r)
|
||||||
@@ -339,6 +339,7 @@ func TestGetMatchingResourcesByAnyId(t *testing.T) {
|
|||||||
"metadata": map[string]interface{}{
|
"metadata": map[string]interface{}{
|
||||||
"name": "new-alice",
|
"name": "new-alice",
|
||||||
"annotations": map[string]interface{}{
|
"annotations": map[string]interface{}{
|
||||||
|
"config.kubernetes.io/previousKinds": "ConfigMap",
|
||||||
"config.kubernetes.io/previousNames": "alice",
|
"config.kubernetes.io/previousNames": "alice",
|
||||||
"config.kubernetes.io/previousNamespaces": "default",
|
"config.kubernetes.io/previousNamespaces": "default",
|
||||||
},
|
},
|
||||||
@@ -351,6 +352,7 @@ func TestGetMatchingResourcesByAnyId(t *testing.T) {
|
|||||||
"metadata": map[string]interface{}{
|
"metadata": map[string]interface{}{
|
||||||
"name": "new-bob",
|
"name": "new-bob",
|
||||||
"annotations": map[string]interface{}{
|
"annotations": map[string]interface{}{
|
||||||
|
"config.kubernetes.io/previousKinds": "ConfigMap,ConfigMap",
|
||||||
"config.kubernetes.io/previousNames": "bob,bob2",
|
"config.kubernetes.io/previousNames": "bob,bob2",
|
||||||
"config.kubernetes.io/previousNamespaces": "default,default",
|
"config.kubernetes.io/previousNamespaces": "default,default",
|
||||||
},
|
},
|
||||||
@@ -364,6 +366,7 @@ func TestGetMatchingResourcesByAnyId(t *testing.T) {
|
|||||||
"name": "new-bob",
|
"name": "new-bob",
|
||||||
"namespace": "new-happy",
|
"namespace": "new-happy",
|
||||||
"annotations": map[string]interface{}{
|
"annotations": map[string]interface{}{
|
||||||
|
"config.kubernetes.io/previousKinds": "ConfigMap",
|
||||||
"config.kubernetes.io/previousNames": "bob",
|
"config.kubernetes.io/previousNames": "bob",
|
||||||
"config.kubernetes.io/previousNamespaces": "happy",
|
"config.kubernetes.io/previousNamespaces": "happy",
|
||||||
},
|
},
|
||||||
@@ -377,6 +380,7 @@ func TestGetMatchingResourcesByAnyId(t *testing.T) {
|
|||||||
"name": "charlie",
|
"name": "charlie",
|
||||||
"namespace": "happy",
|
"namespace": "happy",
|
||||||
"annotations": map[string]interface{}{
|
"annotations": map[string]interface{}{
|
||||||
|
"config.kubernetes.io/previousKinds": "ConfigMap",
|
||||||
"config.kubernetes.io/previousNames": "charlie",
|
"config.kubernetes.io/previousNames": "charlie",
|
||||||
"config.kubernetes.io/previousNamespaces": "default",
|
"config.kubernetes.io/previousNamespaces": "default",
|
||||||
},
|
},
|
||||||
@@ -873,13 +877,8 @@ rules:
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
rnodes, err := rm.ToRNodeSlice()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b := bytes.NewBufferString("")
|
b := bytes.NewBufferString("")
|
||||||
for i, n := range rnodes {
|
for i, n := range rm.ToRNodeSlice() {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
b.WriteString("---\n")
|
b.WriteString("---\n")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,13 +61,13 @@ func TestFindPatchTargets(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
"select_01": {
|
"select_01": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Name: "name.*",
|
KrmId: types.KrmId{Name: "name.*"},
|
||||||
},
|
},
|
||||||
count: 3,
|
count: 3,
|
||||||
},
|
},
|
||||||
"select_02": {
|
"select_02": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Name: "name.*",
|
KrmId: types.KrmId{Name: "name.*"},
|
||||||
AnnotationSelector: "foo=bar",
|
AnnotationSelector: "foo=bar",
|
||||||
},
|
},
|
||||||
count: 2,
|
count: 2,
|
||||||
@@ -80,98 +80,102 @@ func TestFindPatchTargets(t *testing.T) {
|
|||||||
},
|
},
|
||||||
"select_04": {
|
"select_04": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Gvk: resid.Gvk{
|
KrmId: types.KrmId{
|
||||||
Kind: "Kind1",
|
Gvk: resid.Gvk{
|
||||||
|
Kind: "Kind1",
|
||||||
|
},
|
||||||
|
Name: "name.*",
|
||||||
},
|
},
|
||||||
Name: "name.*",
|
|
||||||
},
|
},
|
||||||
count: 2,
|
count: 2,
|
||||||
},
|
},
|
||||||
"select_05": {
|
"select_05": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Name: "NotMatched",
|
KrmId: types.KrmId{Name: "NotMatched"},
|
||||||
},
|
},
|
||||||
count: 0,
|
count: 0,
|
||||||
},
|
},
|
||||||
"select_06": {
|
"select_06": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Name: "",
|
KrmId: types.KrmId{Name: ""},
|
||||||
},
|
},
|
||||||
count: 4,
|
count: 4,
|
||||||
},
|
},
|
||||||
"select_07": {
|
"select_07": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Namespace: "default",
|
KrmId: types.KrmId{Namespace: "default"},
|
||||||
},
|
},
|
||||||
count: 2,
|
count: 2,
|
||||||
},
|
},
|
||||||
"select_08": {
|
"select_08": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Namespace: "",
|
KrmId: types.KrmId{Namespace: ""},
|
||||||
},
|
},
|
||||||
count: 4,
|
count: 4,
|
||||||
},
|
},
|
||||||
"select_09": {
|
"select_09": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Namespace: "default",
|
KrmId: types.KrmId{
|
||||||
Name: "name.*",
|
Namespace: "default",
|
||||||
Gvk: resid.Gvk{
|
Name: "name.*",
|
||||||
Kind: "Kind1",
|
Gvk: resid.Gvk{
|
||||||
|
Kind: "Kind1",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
count: 1,
|
count: 1,
|
||||||
},
|
},
|
||||||
"select_10": {
|
"select_10": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Name: "^name.*",
|
KrmId: types.KrmId{Name: "^name.*"},
|
||||||
},
|
},
|
||||||
count: 3,
|
count: 3,
|
||||||
},
|
},
|
||||||
"select_11": {
|
"select_11": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Name: "name.*$",
|
KrmId: types.KrmId{Name: "name.*$"},
|
||||||
},
|
},
|
||||||
count: 3,
|
count: 3,
|
||||||
},
|
},
|
||||||
"select_12": {
|
"select_12": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Name: "^name.*$",
|
KrmId: types.KrmId{Name: "^name.*$"},
|
||||||
},
|
},
|
||||||
count: 3,
|
count: 3,
|
||||||
},
|
},
|
||||||
"select_13": {
|
"select_13": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Namespace: "^def.*",
|
KrmId: types.KrmId{Namespace: "^def.*"},
|
||||||
},
|
},
|
||||||
count: 2,
|
count: 2,
|
||||||
},
|
},
|
||||||
"select_14": {
|
"select_14": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Namespace: "def.*$",
|
KrmId: types.KrmId{Namespace: "def.*$"},
|
||||||
},
|
},
|
||||||
count: 2,
|
count: 2,
|
||||||
},
|
},
|
||||||
"select_15": {
|
"select_15": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Namespace: "^def.*$",
|
KrmId: types.KrmId{Namespace: "^def.*$"},
|
||||||
},
|
},
|
||||||
count: 2,
|
count: 2,
|
||||||
},
|
},
|
||||||
"select_16": {
|
"select_16": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Namespace: "default",
|
KrmId: types.KrmId{Namespace: "default"},
|
||||||
},
|
},
|
||||||
count: 2,
|
count: 2,
|
||||||
},
|
},
|
||||||
"select_17": {
|
"select_17": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Namespace: "NotMatched",
|
KrmId: types.KrmId{Namespace: "NotMatched"},
|
||||||
},
|
},
|
||||||
count: 0,
|
count: 0,
|
||||||
},
|
},
|
||||||
"select_18": {
|
"select_18": {
|
||||||
target: types.Selector{
|
target: types.Selector{
|
||||||
Namespace: "ns1",
|
KrmId: types.KrmId{Namespace: "ns1"},
|
||||||
},
|
},
|
||||||
count: 1,
|
count: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package resource
|
|
||||||
|
|
||||||
import "sigs.k8s.io/kustomize/api/resid"
|
|
||||||
|
|
||||||
// ConflictDetector detects conflicts between resources.
|
|
||||||
type ConflictDetector interface {
|
|
||||||
// HasConflict returns true if the given resources have a conflict.
|
|
||||||
HasConflict(patch1, patch2 *Resource) (bool, error)
|
|
||||||
// Merge two resources into one.
|
|
||||||
MergePatches(patch1, patch2 *Resource) (*Resource, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConflictDetectorFactory makes instances of ConflictDetector that know
|
|
||||||
// how to handle the given Group, Version, Kind tuple.
|
|
||||||
type ConflictDetectorFactory interface {
|
|
||||||
New(gvk resid.Gvk) (ConflictDetector, error)
|
|
||||||
}
|
|
||||||
@@ -10,28 +10,33 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
|
"sigs.k8s.io/kustomize/api/internal/generators"
|
||||||
"sigs.k8s.io/kustomize/api/internal/kusterr"
|
"sigs.k8s.io/kustomize/api/internal/kusterr"
|
||||||
|
"sigs.k8s.io/kustomize/api/konfig"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Factory makes instances of Resource.
|
// Factory makes instances of Resource.
|
||||||
type Factory struct {
|
type Factory struct {
|
||||||
kf ifc.KunstructuredFactory
|
hasher ifc.KustHasher
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewFactory makes an instance of Factory.
|
// NewFactory makes an instance of Factory.
|
||||||
func NewFactory(kf ifc.KunstructuredFactory) *Factory {
|
func NewFactory(h ifc.KustHasher) *Factory {
|
||||||
return &Factory{kf: kf}
|
return &Factory{hasher: h}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rf *Factory) Hasher() ifc.KunstructuredHasher {
|
// Hasher returns an ifc.KustHasher
|
||||||
return rf.kf.Hasher()
|
func (rf *Factory) Hasher() ifc.KustHasher {
|
||||||
|
return rf.hasher
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromMap returns a new instance of Resource.
|
// FromMap returns a new instance of Resource.
|
||||||
func (rf *Factory) FromMap(m map[string]interface{}) *Resource {
|
func (rf *Factory) FromMap(m map[string]interface{}) *Resource {
|
||||||
return rf.makeOne(rf.kf.FromMap(m), nil)
|
return rf.FromMapAndOption(m, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromMapWithName returns a new instance with the given "original" name.
|
// FromMapWithName returns a new instance with the given "original" name.
|
||||||
@@ -41,34 +46,30 @@ func (rf *Factory) FromMapWithName(n string, m map[string]interface{}) *Resource
|
|||||||
|
|
||||||
// FromMapWithNamespaceAndName returns a new instance with the given "original" namespace.
|
// FromMapWithNamespaceAndName returns a new instance with the given "original" namespace.
|
||||||
func (rf *Factory) FromMapWithNamespaceAndName(ns string, n string, m map[string]interface{}) *Resource {
|
func (rf *Factory) FromMapWithNamespaceAndName(ns string, n string, m map[string]interface{}) *Resource {
|
||||||
return rf.makeOne(rf.kf.FromMap(m), nil).setPreviousNamespaceAndName(ns, n)
|
r := rf.FromMapAndOption(m, nil)
|
||||||
|
return r.setPreviousId(ns, n, r.GetKind())
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromMapAndOption returns a new instance of Resource with given options.
|
// FromMapAndOption returns a new instance of Resource with given options.
|
||||||
func (rf *Factory) FromMapAndOption(
|
func (rf *Factory) FromMapAndOption(
|
||||||
m map[string]interface{}, args *types.GeneratorArgs) *Resource {
|
m map[string]interface{}, args *types.GeneratorArgs) *Resource {
|
||||||
return rf.makeOne(rf.kf.FromMap(m), types.NewGenArgs(args))
|
n, err := yaml.FromMap(m)
|
||||||
}
|
if err != nil {
|
||||||
|
// TODO: return err instead of log.
|
||||||
// FromKunstructured returns a new instance of Resource.
|
log.Fatal(err)
|
||||||
func (rf *Factory) FromKunstructured(u ifc.Kunstructured) *Resource {
|
}
|
||||||
return rf.makeOne(u, nil)
|
return rf.makeOne(n, types.NewGenArgs(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeOne returns a new instance of Resource.
|
// makeOne returns a new instance of Resource.
|
||||||
func (rf *Factory) makeOne(
|
func (rf *Factory) makeOne(rn *yaml.RNode, o *types.GenArgs) *Resource {
|
||||||
u ifc.Kunstructured, o *types.GenArgs) *Resource {
|
if rn == nil {
|
||||||
if u == nil {
|
log.Fatal("RNode must not be null")
|
||||||
log.Fatal("unstruct ifc must not be null")
|
|
||||||
}
|
}
|
||||||
if o == nil {
|
if o == nil {
|
||||||
o = types.NewGenArgs(nil)
|
o = types.NewGenArgs(nil)
|
||||||
}
|
}
|
||||||
r := &Resource{
|
return &Resource{node: rn, options: o}
|
||||||
kunStr: u,
|
|
||||||
options: o,
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SliceFromPatches returns a slice of resources given a patch path
|
// SliceFromPatches returns a slice of resources given a patch path
|
||||||
@@ -105,47 +106,135 @@ func (rf *Factory) FromBytes(in []byte) (*Resource, error) {
|
|||||||
|
|
||||||
// SliceFromBytes unmarshals bytes into a Resource slice.
|
// SliceFromBytes unmarshals bytes into a Resource slice.
|
||||||
func (rf *Factory) SliceFromBytes(in []byte) ([]*Resource, error) {
|
func (rf *Factory) SliceFromBytes(in []byte) ([]*Resource, error) {
|
||||||
kunStructs, err := rf.kf.SliceFromBytes(in)
|
nodes, err := rf.RNodesFromBytes(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var result []*Resource
|
return rf.resourcesFromRNodes(nodes), nil
|
||||||
for len(kunStructs) > 0 {
|
}
|
||||||
u := kunStructs[0]
|
|
||||||
kunStructs = kunStructs[1:]
|
// ResourcesFromRNodes converts RNodes to Resources.
|
||||||
if strings.HasSuffix(u.GetKind(), "List") {
|
func (rf *Factory) ResourcesFromRNodes(
|
||||||
m, err := u.Map()
|
nodes []*yaml.RNode) (result []*Resource, err error) {
|
||||||
if err != nil {
|
nodes, err = rf.dropBadNodes(nodes)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rf.resourcesFromRNodes(nodes), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourcesFromRNode assumes all nodes are good.
|
||||||
|
func (rf *Factory) resourcesFromRNodes(
|
||||||
|
nodes []*yaml.RNode) (result []*Resource) {
|
||||||
|
for _, n := range nodes {
|
||||||
|
result = append(result, rf.makeOne(n, nil))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rf *Factory) RNodesFromBytes(b []byte) (result []*yaml.RNode, err error) {
|
||||||
|
nodes, err := kio.FromBytes(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
nodes, err = rf.dropBadNodes(nodes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for len(nodes) > 0 {
|
||||||
|
n0 := nodes[0]
|
||||||
|
nodes = nodes[1:]
|
||||||
|
kind := n0.GetKind()
|
||||||
|
if !strings.HasSuffix(kind, "List") {
|
||||||
|
result = append(result, n0)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Convert a FooList into a slice of Foo.
|
||||||
|
var m map[string]interface{}
|
||||||
|
m, err = n0.Map()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items, ok := m["items"]
|
||||||
|
if !ok {
|
||||||
|
// treat as an empty list
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slice, ok := items.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
if items == nil {
|
||||||
|
// an empty list
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
items := m["items"]
|
return nil, fmt.Errorf(
|
||||||
itemsSlice, ok := items.([]interface{})
|
"expected array in %s/items, but found %T", kind, items)
|
||||||
if !ok {
|
}
|
||||||
if items == nil {
|
innerNodes, err := rf.convertObjectSliceToNodeSlice(slice)
|
||||||
// an empty list
|
if err != nil {
|
||||||
continue
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("items in List is type %T, expected array", items)
|
nodes = append(nodes, innerNodes...)
|
||||||
}
|
}
|
||||||
for _, item := range itemsSlice {
|
return result, nil
|
||||||
itemJSON, err := json.Marshal(item)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
// convertObjectSlice converts a list of objects to a list of RNode.
|
||||||
}
|
func (rf *Factory) convertObjectSliceToNodeSlice(
|
||||||
innerU, err := rf.kf.SliceFromBytes(itemJSON)
|
objects []interface{}) (result []*yaml.RNode, err error) {
|
||||||
if err != nil {
|
var bytes []byte
|
||||||
return nil, err
|
var nodes []*yaml.RNode
|
||||||
}
|
for _, obj := range objects {
|
||||||
// append innerU to kunStructs so nested Lists can be handled
|
bytes, err = json.Marshal(obj)
|
||||||
kunStructs = append(kunStructs, innerU...)
|
if err != nil {
|
||||||
}
|
return
|
||||||
} else {
|
}
|
||||||
result = append(result, rf.FromKunstructured(u))
|
nodes, err = kio.FromBytes(bytes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nodes, err = rf.dropBadNodes(nodes)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result = append(result, nodes...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// dropBadNodes may drop some nodes from its input argument.
|
||||||
|
func (rf *Factory) dropBadNodes(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||||
|
var result []*yaml.RNode
|
||||||
|
for _, n := range nodes {
|
||||||
|
ignore, err := rf.shouldIgnore(n)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !ignore {
|
||||||
|
result = append(result, n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shouldIgnore returns true if there's some reason to ignore the node.
|
||||||
|
func (rf *Factory) shouldIgnore(n *yaml.RNode) (bool, error) {
|
||||||
|
if n.IsNilOrEmpty() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
md, err := n.GetValidatedMetadata()
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
_, ignore := md.ObjectMeta.Annotations[konfig.IgnoredByKustomizeAnnotation]
|
||||||
|
if ignore {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if foundNil, path := n.HasNilEntryInList(); foundNil {
|
||||||
|
return true, fmt.Errorf("empty item at %v in object %v", path, n)
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
// SliceFromBytesWithNames unmarshals bytes into a Resource slice with specified original
|
// SliceFromBytesWithNames unmarshals bytes into a Resource slice with specified original
|
||||||
// name.
|
// name.
|
||||||
func (rf *Factory) SliceFromBytesWithNames(names []string, in []byte) ([]*Resource, error) {
|
func (rf *Factory) SliceFromBytesWithNames(names []string, in []byte) ([]*Resource, error) {
|
||||||
@@ -157,25 +246,25 @@ func (rf *Factory) SliceFromBytesWithNames(names []string, in []byte) ([]*Resour
|
|||||||
return nil, fmt.Errorf("number of names doesn't match number of resources")
|
return nil, fmt.Errorf("number of names doesn't match number of resources")
|
||||||
}
|
}
|
||||||
for i, res := range result {
|
for i, res := range result {
|
||||||
res.setPreviousNamespaceAndName(resid.DefaultNamespace, names[i])
|
res.setPreviousId(resid.DefaultNamespace, names[i], res.GetKind())
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeConfigMap makes an instance of Resource for ConfigMap
|
// MakeConfigMap makes an instance of Resource for ConfigMap
|
||||||
func (rf *Factory) MakeConfigMap(kvLdr ifc.KvLoader, args *types.ConfigMapArgs) (*Resource, error) {
|
func (rf *Factory) MakeConfigMap(kvLdr ifc.KvLoader, args *types.ConfigMapArgs) (*Resource, error) {
|
||||||
u, err := rf.kf.MakeConfigMap(kvLdr, args)
|
rn, err := generators.MakeConfigMap(kvLdr, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return rf.makeOne(u, types.NewGenArgs(&args.GeneratorArgs)), nil
|
return rf.makeOne(rn, types.NewGenArgs(&args.GeneratorArgs)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeSecret makes an instance of Resource for Secret
|
// MakeSecret makes an instance of Resource for Secret
|
||||||
func (rf *Factory) MakeSecret(kvLdr ifc.KvLoader, args *types.SecretArgs) (*Resource, error) {
|
func (rf *Factory) MakeSecret(kvLdr ifc.KvLoader, args *types.SecretArgs) (*Resource, error) {
|
||||||
u, err := rf.kf.MakeSecret(kvLdr, args)
|
rn, err := generators.MakeSecret(kvLdr, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return rf.makeOne(u, types.NewGenArgs(&args.GeneratorArgs)), nil
|
return rf.makeOne(rn, types.NewGenArgs(&args.GeneratorArgs)), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,9 @@ package resource_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/loader"
|
"sigs.k8s.io/kustomize/api/loader"
|
||||||
@@ -153,35 +152,33 @@ spec:
|
|||||||
for name := range testCases {
|
for name := range testCases {
|
||||||
tc := testCases[name]
|
tc := testCases[name]
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
result, err := factory.SliceFromBytes([]byte(tc.input))
|
result, err := factory.RNodesFromBytes([]byte(tc.input))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%v: fails with err: %v", name, err)
|
t.Fatalf("%v: fails with err: %v", name, err)
|
||||||
}
|
}
|
||||||
if len(result) != len(tc.expected) {
|
if len(result) != len(tc.expected) {
|
||||||
for i := range result {
|
for i := range result {
|
||||||
bytes, err := result[i].AsYAML()
|
str, err := result[i].String()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
|
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
|
||||||
}
|
}
|
||||||
tmp := string(bytes)
|
t.Logf("--- %d:\n%s", i, str)
|
||||||
t.Logf("--- %d:\n%s", i, tmp)
|
|
||||||
}
|
}
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"%v: actual len %d != expected len %d",
|
"%v: actual len %d != expected len %d",
|
||||||
name, len(result), len(tc.expected))
|
name, len(result), len(tc.expected))
|
||||||
}
|
}
|
||||||
for i := range tc.expected {
|
for i := range tc.expected {
|
||||||
bytes, err := result[i].AsYAML()
|
str, err := result[i].String()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
|
t.Fatalf("%v: result to YAML fails with err: %v", name, err)
|
||||||
}
|
}
|
||||||
tmp := string(bytes)
|
if str != tc.expected[i] {
|
||||||
if tmp != tc.expected[i] {
|
|
||||||
t.Fatalf(
|
t.Fatalf(
|
||||||
"%v: string mismatch in item %d\n"+
|
"%v: string mismatch in item %d\n"+
|
||||||
"actual:\n-----\n%s\n-----\n"+
|
"actual:\n-----\n%s\n-----\n"+
|
||||||
"expected:\n-----\n%s\n-----\n",
|
"expected:\n-----\n%s\n-----\n",
|
||||||
name, i, tmp, tc.expected[i])
|
name, i, str, tc.expected[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -309,77 +306,376 @@ kind: List
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := map[string]struct {
|
||||||
name string
|
|
||||||
input []types.PatchStrategicMerge
|
input []types.PatchStrategicMerge
|
||||||
expectedOut []*Resource
|
expectedOut []*Resource
|
||||||
expectedErr bool
|
expectedErr bool
|
||||||
}{
|
}{
|
||||||
{
|
"happy": {
|
||||||
name: "happy",
|
|
||||||
input: []types.PatchStrategicMerge{patchGood1, patchGood2},
|
input: []types.PatchStrategicMerge{patchGood1, patchGood2},
|
||||||
expectedOut: []*Resource{testDeployment, testConfigMap},
|
expectedOut: []*Resource{testDeployment, testConfigMap},
|
||||||
expectedErr: false,
|
expectedErr: false,
|
||||||
},
|
},
|
||||||
{
|
"badFileName": {
|
||||||
name: "badFileName",
|
|
||||||
input: []types.PatchStrategicMerge{patchGood1, "doesNotExist"},
|
input: []types.PatchStrategicMerge{patchGood1, "doesNotExist"},
|
||||||
expectedOut: []*Resource{},
|
expectedOut: []*Resource{},
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
},
|
},
|
||||||
{
|
"badData": {
|
||||||
name: "badData",
|
|
||||||
input: []types.PatchStrategicMerge{patchGood1, patchBad},
|
input: []types.PatchStrategicMerge{patchGood1, patchBad},
|
||||||
expectedOut: []*Resource{},
|
expectedOut: []*Resource{},
|
||||||
expectedErr: true,
|
expectedErr: true,
|
||||||
},
|
},
|
||||||
{
|
"listOfPatches": {
|
||||||
name: "listOfPatches",
|
|
||||||
input: []types.PatchStrategicMerge{patchList},
|
input: []types.PatchStrategicMerge{patchList},
|
||||||
expectedOut: []*Resource{testDeployment, testConfigMap},
|
expectedOut: []*Resource{testDeployment, testConfigMap},
|
||||||
expectedErr: false,
|
expectedErr: false,
|
||||||
},
|
},
|
||||||
{
|
"listWithAnchorReference": {
|
||||||
name: "listWithAnchorReference",
|
|
||||||
input: []types.PatchStrategicMerge{patchList2},
|
input: []types.PatchStrategicMerge{patchList2},
|
||||||
expectedOut: []*Resource{testDeploymentA, testDeploymentB},
|
expectedOut: []*Resource{testDeploymentA, testDeploymentB},
|
||||||
// The error using kyaml is:
|
// The error using kyaml is:
|
||||||
// json: unsupported type: map[interface {}]interface {}
|
// json: unsupported type: map[interface {}]interface {}
|
||||||
// maybe arising from too many conversions between
|
// maybe arising from too many conversions between
|
||||||
// yaml, json, Resource, RNode, Unstructured etc.
|
// yaml, json, Resource, RNode, etc.
|
||||||
// These conversions go away after closing #3506
|
// These conversions go away after closing #3506
|
||||||
// TODO(#3271) This shouldn't have an error, but does when kyaml is used.
|
// TODO(#3271) This shouldn't have an error, but does when kyaml is used.
|
||||||
// TODO(#3304): DECISION - still a bug, but not a blocker to #3304 or #2506
|
expectedErr: true,
|
||||||
expectedErr: konfig.FlagEnableKyamlDefaultValue,
|
|
||||||
},
|
},
|
||||||
{
|
"listWithNoEntries": {
|
||||||
name: "listWithNoEntries",
|
|
||||||
input: []types.PatchStrategicMerge{patchList3},
|
input: []types.PatchStrategicMerge{patchList3},
|
||||||
expectedOut: []*Resource{},
|
expectedOut: []*Resource{},
|
||||||
expectedErr: false,
|
expectedErr: false,
|
||||||
},
|
},
|
||||||
{
|
"listWithNoItems": {
|
||||||
name: "listWithNoItems",
|
|
||||||
input: []types.PatchStrategicMerge{patchList4},
|
input: []types.PatchStrategicMerge{patchList4},
|
||||||
expectedOut: []*Resource{},
|
expectedOut: []*Resource{},
|
||||||
expectedErr: false,
|
expectedErr: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for n, test := range tests {
|
||||||
rs, err := factory.SliceFromPatches(ldr, test.input)
|
t.Run(n, func(t *testing.T) {
|
||||||
if err != nil {
|
rs, err := factory.SliceFromPatches(ldr, test.input)
|
||||||
assert.True(t, test.expectedErr,
|
if err != nil {
|
||||||
fmt.Sprintf("in test %s, got unexpected error: %v", test.name, err))
|
assert.True(t, test.expectedErr,
|
||||||
continue
|
fmt.Sprintf("in test %s, got unexpected error: %v", n, err))
|
||||||
}
|
return
|
||||||
assert.False(t, test.expectedErr, "expected no error in "+test.name)
|
}
|
||||||
assert.Equal(t, len(test.expectedOut), len(rs))
|
assert.False(t, test.expectedErr, "expected no error in "+n)
|
||||||
for i := range rs {
|
assert.Equal(t, len(test.expectedOut), len(rs))
|
||||||
expYaml, err := test.expectedOut[i].AsYAML()
|
for i := range rs {
|
||||||
assert.NoError(t, err)
|
expYaml, err := test.expectedOut[i].AsYAML()
|
||||||
actYaml, err := rs[i].AsYAML()
|
assert.NoError(t, err)
|
||||||
assert.NoError(t, err)
|
actYaml, err := rs[i].AsYAML()
|
||||||
assert.Equal(t, expYaml, actYaml)
|
assert.NoError(t, err)
|
||||||
}
|
assert.Equal(t, expYaml, actYaml)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHash(t *testing.T) {
|
||||||
|
input := `
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
data:
|
||||||
|
one: ""
|
||||||
|
binaryData:
|
||||||
|
two: ""
|
||||||
|
`
|
||||||
|
expect := "698h7c7t9m"
|
||||||
|
k, err := factory.SliceFromBytes([]byte(input))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
result, err := k[0].Hash(factory.Hasher())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if result != expect {
|
||||||
|
t.Fatalf("expect %s but got %s", expect, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSliceFromBytesMore(t *testing.T) {
|
||||||
|
testConfigMap :=
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "winnie",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testDeploymentSpec := map[string]interface{}{
|
||||||
|
"template": map[string]interface{}{
|
||||||
|
"spec": map[string]interface{}{
|
||||||
|
"hostAliases": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"hostnames": []interface{}{
|
||||||
|
"a.example.com",
|
||||||
|
},
|
||||||
|
"ip": "8.8.8.8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testDeploymentA := map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deployment-a",
|
||||||
|
},
|
||||||
|
"spec": testDeploymentSpec,
|
||||||
|
}
|
||||||
|
testDeploymentB := map[string]interface{}{
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"kind": "Deployment",
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": "deployment-b",
|
||||||
|
},
|
||||||
|
"spec": testDeploymentSpec,
|
||||||
|
}
|
||||||
|
testDeploymentList :=
|
||||||
|
map[string]interface{}{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "DeploymentList",
|
||||||
|
"items": []interface{}{
|
||||||
|
testDeploymentA,
|
||||||
|
testDeploymentB,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
type expected struct {
|
||||||
|
out []map[string]interface{}
|
||||||
|
isErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
input []byte
|
||||||
|
exp expected
|
||||||
|
}{
|
||||||
|
"garbage": {
|
||||||
|
input: []byte("garbageIn: garbageOut"),
|
||||||
|
exp: expected{
|
||||||
|
isErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"noBytes": {
|
||||||
|
input: []byte{},
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"goodJson": {
|
||||||
|
input: []byte(`
|
||||||
|
{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{testConfigMap},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"goodYaml1": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{testConfigMap},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"goodYaml2": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{testConfigMap, testConfigMap},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"localConfigYaml": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie-skip
|
||||||
|
annotations:
|
||||||
|
# this annotation causes the Resource to be ignored by kustomize
|
||||||
|
config.kubernetes.io/local-config: ""
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{testConfigMap},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"garbageInOneOfTwoObjects": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
---
|
||||||
|
WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
isErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"emptyObjects": {
|
||||||
|
input: []byte(`
|
||||||
|
---
|
||||||
|
#a comment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"Missing .metadata.name in object": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
annotations:
|
||||||
|
foo: bar
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
isErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nil value in list": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: builtin
|
||||||
|
kind: ConfigMapGenerator
|
||||||
|
metadata:
|
||||||
|
name: kube100-site
|
||||||
|
labels:
|
||||||
|
app: web
|
||||||
|
testList:
|
||||||
|
- testA
|
||||||
|
-
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
isErr: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"List": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: List
|
||||||
|
items:
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{
|
||||||
|
testConfigMap,
|
||||||
|
testConfigMap},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"ConfigMapList": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMapList
|
||||||
|
items:
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
- apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: winnie
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
out: []map[string]interface{}{
|
||||||
|
testConfigMap,
|
||||||
|
testConfigMap,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"listWithAnchors": {
|
||||||
|
input: []byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: DeploymentList
|
||||||
|
items:
|
||||||
|
- apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deployment-a
|
||||||
|
spec: &hostAliases
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
hostAliases:
|
||||||
|
- hostnames:
|
||||||
|
- a.example.com
|
||||||
|
ip: 8.8.8.8
|
||||||
|
- apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deployment-b
|
||||||
|
spec:
|
||||||
|
<<: *hostAliases
|
||||||
|
`),
|
||||||
|
exp: expected{
|
||||||
|
// TODO(3271): This should work.
|
||||||
|
// https://github.com/kubernetes-sigs/kustomize/issues/3271
|
||||||
|
// json.Marshal(obj) fails on the 2nd list item.
|
||||||
|
// The value of the 1st list item's first spec field is
|
||||||
|
// map[string]interface{}
|
||||||
|
// The value of the 2nd list item's first spec field is
|
||||||
|
// map[interface{}]interface{}
|
||||||
|
// which causes a encoding/json.UnsupportedTypeError.
|
||||||
|
isErr: true,
|
||||||
|
out: []map[string]interface{}{testDeploymentList},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for n := range testCases {
|
||||||
|
tc := testCases[n]
|
||||||
|
t.Run(n, func(t *testing.T) {
|
||||||
|
rs, err := factory.RNodesFromBytes(tc.input)
|
||||||
|
if err != nil {
|
||||||
|
assert.True(t, tc.exp.isErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.False(t, tc.exp.isErr)
|
||||||
|
assert.Equal(t, len(tc.exp.out), len(rs))
|
||||||
|
for i := range rs {
|
||||||
|
rsMap, err := rs[i].Map()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(
|
||||||
|
t, fmt.Sprintf("%v", tc.exp.out[i]), fmt.Sprintf("%v", rsMap))
|
||||||
|
m, _ := rs[i].Map()
|
||||||
|
if !reflect.DeepEqual(tc.exp.out[i], m) {
|
||||||
|
t.Fatalf("%s:\nexpected: %v\n actual: %v",
|
||||||
|
n, tc.exp.out[i], m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,153 +12,184 @@ import (
|
|||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
|
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
"sigs.k8s.io/kustomize/api/ifc"
|
||||||
"sigs.k8s.io/kustomize/api/internal/wrappy"
|
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
"sigs.k8s.io/kustomize/api/konfig"
|
||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
"sigs.k8s.io/kustomize/api/types"
|
||||||
"sigs.k8s.io/kustomize/kyaml/filtersutil"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Resource is a representation of a Kubernetes Resource Model (KRM) object
|
// Resource is an RNode, representing a Kubernetes Resource Model object,
|
||||||
// paired with metadata used by kustomize.
|
// paired with metadata used by kustomize.
|
||||||
// For more history, see sigs.k8s.io/kustomize/api/ifc.Unstructured
|
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
kunStr ifc.Kunstructured
|
// TODO: Inline RNode, dropping complexity. Resource is just a decorator.
|
||||||
|
node *kyaml.RNode
|
||||||
options *types.GenArgs
|
options *types.GenArgs
|
||||||
refBy []resid.ResId
|
refBy []resid.ResId
|
||||||
refVarNames []string
|
refVarNames []string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
buildAnnotationPreviousKinds = konfig.ConfigAnnoDomain + "/previousKinds"
|
||||||
buildAnnotationPreviousNames = konfig.ConfigAnnoDomain + "/previousNames"
|
buildAnnotationPreviousNames = konfig.ConfigAnnoDomain + "/previousNames"
|
||||||
buildAnnotationPrefixes = konfig.ConfigAnnoDomain + "/prefixes"
|
buildAnnotationPrefixes = konfig.ConfigAnnoDomain + "/prefixes"
|
||||||
buildAnnotationSuffixes = konfig.ConfigAnnoDomain + "/suffixes"
|
buildAnnotationSuffixes = konfig.ConfigAnnoDomain + "/suffixes"
|
||||||
buildAnnotationPreviousNamespaces = konfig.ConfigAnnoDomain + "/previousNamespaces"
|
buildAnnotationPreviousNamespaces = konfig.ConfigAnnoDomain + "/previousNamespaces"
|
||||||
|
|
||||||
|
// the following are only for patches, to specify whether they can change names
|
||||||
|
// and kinds of their targets
|
||||||
|
buildAnnotationAllowNameChange = konfig.ConfigAnnoDomain + "/allowNameChange"
|
||||||
|
buildAnnotationAllowKindChange = konfig.ConfigAnnoDomain + "/allowKindChange"
|
||||||
)
|
)
|
||||||
|
|
||||||
var buildAnnotations = []string{
|
var buildAnnotations = []string{
|
||||||
|
buildAnnotationPreviousKinds,
|
||||||
buildAnnotationPreviousNames,
|
buildAnnotationPreviousNames,
|
||||||
buildAnnotationPrefixes,
|
buildAnnotationPrefixes,
|
||||||
buildAnnotationSuffixes,
|
buildAnnotationSuffixes,
|
||||||
buildAnnotationPreviousNamespaces,
|
buildAnnotationPreviousNamespaces,
|
||||||
|
buildAnnotationAllowNameChange,
|
||||||
|
buildAnnotationAllowKindChange,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) AsRNode() *kyaml.RNode {
|
||||||
|
return r.node.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) ResetPrimaryData(incoming *Resource) {
|
func (r *Resource) ResetPrimaryData(incoming *Resource) {
|
||||||
r.kunStr = incoming.Copy()
|
r.node = incoming.node.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetAnnotations() map[string]string {
|
func (r *Resource) GetAnnotations() map[string]string {
|
||||||
annotations := r.kunStr.GetAnnotations()
|
annotations, err := r.node.GetAnnotations()
|
||||||
if annotations == nil {
|
if err != nil || annotations == nil {
|
||||||
return make(map[string]string)
|
return make(map[string]string)
|
||||||
}
|
}
|
||||||
return annotations
|
return annotations
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) Copy() ifc.Kunstructured {
|
|
||||||
return r.kunStr.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resource) GetFieldValue(f string) (interface{}, error) {
|
func (r *Resource) GetFieldValue(f string) (interface{}, error) {
|
||||||
return r.kunStr.GetFieldValue(f)
|
//nolint:staticcheck
|
||||||
|
return r.node.GetFieldValue(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetDataMap() map[string]string {
|
func (r *Resource) GetDataMap() map[string]string {
|
||||||
return r.kunStr.GetDataMap()
|
return r.node.GetDataMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetBinaryDataMap() map[string]string {
|
func (r *Resource) GetBinaryDataMap() map[string]string {
|
||||||
return r.kunStr.GetBinaryDataMap()
|
return r.node.GetBinaryDataMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetGvk() resid.Gvk {
|
func (r *Resource) GetGvk() resid.Gvk {
|
||||||
return r.kunStr.GetGvk()
|
meta, err := r.node.GetMeta()
|
||||||
|
if err != nil {
|
||||||
|
return resid.GvkFromString("")
|
||||||
|
}
|
||||||
|
g, v := resid.ParseGroupVersion(meta.APIVersion)
|
||||||
|
return resid.Gvk{Group: g, Version: v, Kind: meta.Kind}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) Hash(h ifc.KustHasher) (string, error) {
|
||||||
|
return h.Hash(r.node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetKind() string {
|
func (r *Resource) GetKind() string {
|
||||||
return r.kunStr.GetKind()
|
return r.node.GetKind()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetLabels() map[string]string {
|
func (r *Resource) GetLabels() map[string]string {
|
||||||
return r.kunStr.GetLabels()
|
l, err := r.node.GetLabels()
|
||||||
|
if err != nil {
|
||||||
|
return map[string]string{}
|
||||||
|
}
|
||||||
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetName() string {
|
func (r *Resource) GetName() string {
|
||||||
return r.kunStr.GetName()
|
return r.node.GetName()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetSlice(p string) ([]interface{}, error) {
|
func (r *Resource) GetSlice(p string) ([]interface{}, error) {
|
||||||
return r.kunStr.GetSlice(p)
|
//nolint:staticcheck
|
||||||
|
return r.node.GetSlice(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) GetString(p string) (string, error) {
|
func (r *Resource) GetString(p string) (string, error) {
|
||||||
return r.kunStr.GetString(p)
|
//nolint:staticcheck
|
||||||
|
return r.node.GetString(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) IsEmpty() (bool, error) {
|
func (r *Resource) IsEmpty() bool {
|
||||||
m, err := r.kunStr.Map()
|
return r.node.IsNilOrEmpty()
|
||||||
return len(m) == 0, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) Map() (map[string]interface{}, error) {
|
func (r *Resource) Map() (map[string]interface{}, error) {
|
||||||
return r.kunStr.Map()
|
return r.node.Map()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) MarshalJSON() ([]byte, error) {
|
func (r *Resource) MarshalJSON() ([]byte, error) {
|
||||||
return r.kunStr.MarshalJSON()
|
return r.node.MarshalJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) MatchesLabelSelector(selector string) (bool, error) {
|
func (r *Resource) MatchesLabelSelector(selector string) (bool, error) {
|
||||||
return r.kunStr.MatchesLabelSelector(selector)
|
return r.node.MatchesLabelSelector(selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) MatchesAnnotationSelector(selector string) (bool, error) {
|
func (r *Resource) MatchesAnnotationSelector(selector string) (bool, error) {
|
||||||
return r.kunStr.MatchesAnnotationSelector(selector)
|
return r.node.MatchesAnnotationSelector(selector)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetAnnotations(m map[string]string) {
|
func (r *Resource) SetAnnotations(m map[string]string) {
|
||||||
if len(m) == 0 {
|
if len(m) == 0 {
|
||||||
// Force field erasure.
|
// Force field erasure.
|
||||||
r.kunStr.SetAnnotations(nil)
|
r.node.SetAnnotations(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.kunStr.SetAnnotations(m)
|
r.node.SetAnnotations(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetDataMap(m map[string]string) {
|
func (r *Resource) SetDataMap(m map[string]string) {
|
||||||
r.kunStr.SetDataMap(m)
|
r.node.SetDataMap(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetBinaryDataMap(m map[string]string) {
|
func (r *Resource) SetBinaryDataMap(m map[string]string) {
|
||||||
r.kunStr.SetBinaryDataMap(m)
|
r.node.SetBinaryDataMap(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetGvk(gvk resid.Gvk) {
|
func (r *Resource) SetGvk(gvk resid.Gvk) {
|
||||||
r.kunStr.SetGvk(gvk)
|
r.node.SetMapField(
|
||||||
|
kyaml.NewScalarRNode(gvk.Kind), kyaml.KindField)
|
||||||
|
r.node.SetMapField(
|
||||||
|
kyaml.NewScalarRNode(gvk.ApiVersion()), kyaml.APIVersionField)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetLabels(m map[string]string) {
|
func (r *Resource) SetLabels(m map[string]string) {
|
||||||
if len(m) == 0 {
|
if len(m) == 0 {
|
||||||
// Force field erasure.
|
// Force field erasure.
|
||||||
r.kunStr.SetLabels(nil)
|
r.node.SetLabels(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.kunStr.SetLabels(m)
|
r.node.SetLabels(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetName(n string) {
|
func (r *Resource) SetName(n string) {
|
||||||
r.kunStr.SetName(n)
|
r.node.SetName(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) SetNamespace(n string) {
|
func (r *Resource) SetNamespace(n string) {
|
||||||
r.kunStr.SetNamespace(n)
|
r.node.SetNamespace(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) SetKind(k string) {
|
||||||
|
gvk := r.GetGvk()
|
||||||
|
gvk.Kind = k
|
||||||
|
r.SetGvk(gvk)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) UnmarshalJSON(s []byte) error {
|
func (r *Resource) UnmarshalJSON(s []byte) error {
|
||||||
return r.kunStr.UnmarshalJSON(s)
|
return r.node.UnmarshalJSON(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResCtx is an interface describing the contextual added
|
// ResCtx is an interface describing the contextual added
|
||||||
@@ -178,14 +209,14 @@ type ResCtxMatcher func(ResCtx) bool
|
|||||||
// DeepCopy returns a new copy of resource
|
// DeepCopy returns a new copy of resource
|
||||||
func (r *Resource) DeepCopy() *Resource {
|
func (r *Resource) DeepCopy() *Resource {
|
||||||
rc := &Resource{
|
rc := &Resource{
|
||||||
kunStr: r.Copy(),
|
node: r.node.Copy(),
|
||||||
}
|
}
|
||||||
rc.copyOtherFields(r)
|
rc.copyOtherFields(r)
|
||||||
return rc
|
return rc
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyMergeMetaDataFields copies everything but the non-metadata in
|
// CopyMergeMetaDataFields copies everything but the non-metadata in
|
||||||
// the ifc.Kunstructured map, merging labels and annotations.
|
// the resource.
|
||||||
func (r *Resource) CopyMergeMetaDataFieldsFrom(other *Resource) {
|
func (r *Resource) CopyMergeMetaDataFieldsFrom(other *Resource) {
|
||||||
r.SetLabels(mergeStringMaps(other.GetLabels(), r.GetLabels()))
|
r.SetLabels(mergeStringMaps(other.GetLabels(), r.GetLabels()))
|
||||||
r.SetAnnotations(
|
r.SetAnnotations(
|
||||||
@@ -251,8 +282,10 @@ func (r *Resource) ReferencesEqual(other *Resource) bool {
|
|||||||
return len(setSelf) == len(setOther)
|
return len(setSelf) == len(setOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) KunstructEqual(o *Resource) bool {
|
// NodeEqual returns true if the resource's nodes are
|
||||||
return reflect.DeepEqual(r.kunStr, o.kunStr)
|
// equal, ignoring ancillary information like genargs, refby, etc.
|
||||||
|
func (r *Resource) NodeEqual(o *Resource) bool {
|
||||||
|
return reflect.DeepEqual(r.node, o.node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) copyRefBy() []resid.ResId {
|
func (r *Resource) copyRefBy() []resid.ResId {
|
||||||
@@ -351,12 +384,41 @@ func (r *Resource) RemoveBuildAnnotations() {
|
|||||||
r.SetAnnotations(annotations)
|
r.SetAnnotations(annotations)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) setPreviousNamespaceAndName(ns string, n string) *Resource {
|
func (r *Resource) setPreviousId(ns string, n string, k string) *Resource {
|
||||||
r.appendCsvAnnotation(buildAnnotationPreviousNames, n)
|
r.appendCsvAnnotation(buildAnnotationPreviousNames, n)
|
||||||
r.appendCsvAnnotation(buildAnnotationPreviousNamespaces, ns)
|
r.appendCsvAnnotation(buildAnnotationPreviousNamespaces, ns)
|
||||||
|
r.appendCsvAnnotation(buildAnnotationPreviousKinds, k)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Resource) SetAllowNameChange(value string) {
|
||||||
|
annotations := r.GetAnnotations()
|
||||||
|
annotations[buildAnnotationAllowNameChange] = value
|
||||||
|
r.SetAnnotations(annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) NameChangeAllowed() bool {
|
||||||
|
annotations := r.GetAnnotations()
|
||||||
|
if allowed, set := annotations[buildAnnotationAllowNameChange]; set && allowed == "true" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) SetAllowKindChange(value string) {
|
||||||
|
annotations := r.GetAnnotations()
|
||||||
|
annotations[buildAnnotationAllowKindChange] = value
|
||||||
|
r.SetAnnotations(annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Resource) KindChangeAllowed() bool {
|
||||||
|
annotations := r.GetAnnotations()
|
||||||
|
if allowed, set := annotations[buildAnnotationAllowKindChange]; set && allowed == "true" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// String returns resource as JSON.
|
// String returns resource as JSON.
|
||||||
func (r *Resource) String() string {
|
func (r *Resource) String() string {
|
||||||
bs, err := r.MarshalJSON()
|
bs, err := r.MarshalJSON()
|
||||||
@@ -430,14 +492,19 @@ func (r *Resource) PrevIds() []resid.ResId {
|
|||||||
// pairs on one annotation so there is no chance of error
|
// pairs on one annotation so there is no chance of error
|
||||||
names := r.getCsvAnnotation(buildAnnotationPreviousNames)
|
names := r.getCsvAnnotation(buildAnnotationPreviousNames)
|
||||||
ns := r.getCsvAnnotation(buildAnnotationPreviousNamespaces)
|
ns := r.getCsvAnnotation(buildAnnotationPreviousNamespaces)
|
||||||
if len(names) != len(ns) {
|
kinds := r.getCsvAnnotation(buildAnnotationPreviousKinds)
|
||||||
|
if len(names) != len(ns) || len(names) != len(kinds) {
|
||||||
panic(errors.New(
|
panic(errors.New(
|
||||||
"number of previous names not equal to " +
|
"number of previous names, " +
|
||||||
"number of previous namespaces"))
|
"number of previous namespaces, " +
|
||||||
|
"number of previous kinds not equal"))
|
||||||
}
|
}
|
||||||
for i := range names {
|
for i := range names {
|
||||||
|
k := kinds[i]
|
||||||
|
gvk := r.GetGvk()
|
||||||
|
gvk.Kind = k
|
||||||
ids = append(ids, resid.NewResIdWithNamespace(
|
ids = append(ids, resid.NewResIdWithNamespace(
|
||||||
r.GetGvk(), names[i], ns[i]))
|
gvk, names[i], ns[i]))
|
||||||
}
|
}
|
||||||
return ids
|
return ids
|
||||||
}
|
}
|
||||||
@@ -445,7 +512,7 @@ func (r *Resource) PrevIds() []resid.ResId {
|
|||||||
// StorePreviousId stores the resource's current ID via build annotations.
|
// StorePreviousId stores the resource's current ID via build annotations.
|
||||||
func (r *Resource) StorePreviousId() {
|
func (r *Resource) StorePreviousId() {
|
||||||
id := r.CurId()
|
id := r.CurId()
|
||||||
r.setPreviousNamespaceAndName(id.EffectiveNamespace(), id.Name)
|
r.setPreviousId(id.EffectiveNamespace(), id.Name, id.Kind)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurId returns a ResId for the resource using the
|
// CurId returns a ResId for the resource using the
|
||||||
@@ -478,38 +545,35 @@ func (r *Resource) AppendRefVarName(variable types.Var) {
|
|||||||
|
|
||||||
// ApplySmPatch applies the provided strategic merge patch.
|
// ApplySmPatch applies the provided strategic merge patch.
|
||||||
func (r *Resource) ApplySmPatch(patch *Resource) error {
|
func (r *Resource) ApplySmPatch(patch *Resource) error {
|
||||||
node, err := filtersutil.GetRNode(patch)
|
n, ns, k := r.GetName(), r.GetNamespace(), r.GetKind()
|
||||||
if err != nil {
|
if patch.NameChangeAllowed() || patch.KindChangeAllowed() {
|
||||||
|
r.StorePreviousId()
|
||||||
|
}
|
||||||
|
if err := r.ApplyFilter(patchstrategicmerge.Filter{
|
||||||
|
Patch: patch.node,
|
||||||
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
n, ns := r.GetName(), r.GetNamespace()
|
if r.IsEmpty() {
|
||||||
err = r.ApplyFilter(patchstrategicmerge.Filter{
|
return nil
|
||||||
Patch: node,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
empty, err := r.IsEmpty()
|
if !patch.KindChangeAllowed() {
|
||||||
if err != nil {
|
r.SetKind(k)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if !empty {
|
if !patch.NameChangeAllowed() {
|
||||||
r.SetName(n)
|
r.SetName(n)
|
||||||
r.SetNamespace(ns)
|
|
||||||
}
|
}
|
||||||
return err
|
r.SetNamespace(ns)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Resource) ApplyFilter(f kio.Filter) error {
|
func (r *Resource) ApplyFilter(f kio.Filter) error {
|
||||||
if wn, ok := r.kunStr.(*wrappy.WNode); ok {
|
l, err := f.Filter([]*kyaml.RNode{r.node})
|
||||||
l, err := f.Filter([]*kyaml.RNode{wn.AsRNode()})
|
if len(l) == 0 {
|
||||||
if len(l) == 0 {
|
// The node was deleted. The following makes r.IsEmpty() true.
|
||||||
// Hack to deal with deletion.
|
r.node = nil
|
||||||
r.kunStr = wrappy.NewWNode()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return filtersutil.ApplyToJSON(f, r)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeStringMaps(maps ...map[string]string) map[string]string {
|
func mergeStringMaps(maps ...map[string]string) map[string]string {
|
||||||
|
|||||||
@@ -695,7 +695,7 @@ spec:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResource_StorePreviousId(t *testing.T) {
|
func TestResourceStorePreviousId(t *testing.T) {
|
||||||
tests := map[string]struct {
|
tests := map[string]struct {
|
||||||
input string
|
input string
|
||||||
newName string
|
newName string
|
||||||
@@ -714,6 +714,7 @@ metadata:
|
|||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
config.kubernetes.io/previousKinds: Secret
|
||||||
config.kubernetes.io/previousNames: oldName
|
config.kubernetes.io/previousNames: oldName
|
||||||
config.kubernetes.io/previousNamespaces: default
|
config.kubernetes.io/previousNamespaces: default
|
||||||
name: newName
|
name: newName
|
||||||
@@ -725,6 +726,7 @@ metadata:
|
|||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
config.kubernetes.io/previousKinds: Secret
|
||||||
config.kubernetes.io/previousNames: oldName
|
config.kubernetes.io/previousNames: oldName
|
||||||
config.kubernetes.io/previousNamespaces: default
|
config.kubernetes.io/previousNamespaces: default
|
||||||
name: oldName2
|
name: oldName2
|
||||||
@@ -735,6 +737,7 @@ metadata:
|
|||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
config.kubernetes.io/previousKinds: Secret,Secret
|
||||||
config.kubernetes.io/previousNames: oldName,oldName2
|
config.kubernetes.io/previousNames: oldName,oldName2
|
||||||
config.kubernetes.io/previousNamespaces: default,default
|
config.kubernetes.io/previousNamespaces: default,default
|
||||||
name: newName
|
name: newName
|
||||||
@@ -746,6 +749,7 @@ metadata:
|
|||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
config.kubernetes.io/previousKinds: Secret
|
||||||
config.kubernetes.io/previousNames: oldName
|
config.kubernetes.io/previousNames: oldName
|
||||||
config.kubernetes.io/previousNamespaces: default
|
config.kubernetes.io/previousNamespaces: default
|
||||||
name: oldName2
|
name: oldName2
|
||||||
@@ -757,6 +761,7 @@ metadata:
|
|||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
config.kubernetes.io/previousKinds: Secret,Secret
|
||||||
config.kubernetes.io/previousNames: oldName,oldName2
|
config.kubernetes.io/previousNames: oldName,oldName2
|
||||||
config.kubernetes.io/previousNamespaces: default,oldNamespace
|
config.kubernetes.io/previousNamespaces: default,oldNamespace
|
||||||
name: newName
|
name: newName
|
||||||
@@ -806,6 +811,7 @@ metadata:
|
|||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
config.kubernetes.io/previousKinds: Secret
|
||||||
config.kubernetes.io/previousNames: oldName
|
config.kubernetes.io/previousNames: oldName
|
||||||
config.kubernetes.io/previousNamespaces: default
|
config.kubernetes.io/previousNamespaces: default
|
||||||
name: newName
|
name: newName
|
||||||
@@ -824,6 +830,7 @@ metadata:
|
|||||||
kind: Secret
|
kind: Secret
|
||||||
metadata:
|
metadata:
|
||||||
annotations:
|
annotations:
|
||||||
|
config.kubernetes.io/previousKinds: Secret,Secret
|
||||||
config.kubernetes.io/previousNames: oldName,oldName2
|
config.kubernetes.io/previousNames: oldName,oldName2
|
||||||
config.kubernetes.io/previousNamespaces: default,oldNamespace
|
config.kubernetes.io/previousNamespaces: default,oldNamespace
|
||||||
name: newName
|
name: newName
|
||||||
@@ -1072,3 +1079,54 @@ func TestSameEndingSubarray(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetGvk(t *testing.T) {
|
||||||
|
r, err := factory.FromBytes([]byte(`
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: clown
|
||||||
|
spec:
|
||||||
|
numReplicas: 1
|
||||||
|
`))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
gvk := r.GetGvk()
|
||||||
|
expected := "apps"
|
||||||
|
actual := gvk.Group
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
expected = "v1"
|
||||||
|
actual = gvk.Version
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
expected = "Deployment"
|
||||||
|
actual = gvk.Kind
|
||||||
|
if expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestSetGvk(t *testing.T) {
|
||||||
|
r, err := factory.FromBytes([]byte(`
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: clown
|
||||||
|
spec:
|
||||||
|
numReplicas: 1
|
||||||
|
`))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
r.SetGvk(resid.GvkFromString("grp_ver_knd"))
|
||||||
|
gvk := r.GetGvk()
|
||||||
|
if expected, actual := "grp", gvk.Group; expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
if expected, actual := "ver", gvk.Version; expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
if expected, actual := "knd", gvk.Kind; expected != actual {
|
||||||
|
t.Fatalf("expected '%s', got '%s'", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ package kusttest_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
@@ -77,15 +76,7 @@ func (th Harness) MakeOptionsPluginsDisabled() krusty.Options {
|
|||||||
|
|
||||||
// Enables use of non-builtin plugins.
|
// Enables use of non-builtin plugins.
|
||||||
func (th Harness) MakeOptionsPluginsEnabled() krusty.Options {
|
func (th Harness) MakeOptionsPluginsEnabled() krusty.Options {
|
||||||
pc, err := konfig.EnabledPluginConfig(types.BploLoadFromFileSys)
|
pc := types.EnabledPluginConfig(types.BploLoadFromFileSys)
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "unable to find plugin root") {
|
|
||||||
th.t.Log(
|
|
||||||
"Tests that want to run with plugins enabled must be " +
|
|
||||||
"bookended by calls to MakeEnhancedHarness(), Reset().")
|
|
||||||
}
|
|
||||||
th.t.Fatal(err)
|
|
||||||
}
|
|
||||||
o := *krusty.MakeDefaultOptions()
|
o := *krusty.MakeDefaultOptions()
|
||||||
o.PluginConfig = pc
|
o.PluginConfig = pc
|
||||||
return o
|
return o
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
package kusttest_test
|
package kusttest_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
@@ -33,39 +37,77 @@ type HarnessEnhanced struct {
|
|||||||
// A file loader using the Harness.fSys to read test data.
|
// A file loader using the Harness.fSys to read test data.
|
||||||
ldr ifc.Loader
|
ldr ifc.Loader
|
||||||
|
|
||||||
|
// If true, wipe the ifc.loader root (not the plugin loader root)
|
||||||
|
// as part of cleanup.
|
||||||
|
shouldWipeLdrRoot bool
|
||||||
|
|
||||||
// A plugin loader that loads plugins from a (real) file system.
|
// A plugin loader that loads plugins from a (real) file system.
|
||||||
pl *pLdr.Loader
|
pl *pLdr.Loader
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeEnhancedHarness(t *testing.T) *HarnessEnhanced {
|
func MakeEnhancedHarness(t *testing.T) *HarnessEnhanced {
|
||||||
pte := newPluginTestEnv(t).set()
|
r := makeBaseEnhancedHarness(t)
|
||||||
|
r.Harness = MakeHarnessWithFs(t, filesys.MakeFsInMemory())
|
||||||
|
// Point the Harness's file loader to the root ('/')
|
||||||
|
// of the in-memory file system.
|
||||||
|
r.ResetLoaderRoot(filesys.Separator)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
pc, err := konfig.EnabledPluginConfig(types.BploLoadFromFileSys)
|
func MakeEnhancedHarnessWithTmpRoot(t *testing.T) *HarnessEnhanced {
|
||||||
pc.FnpLoadingOptions.EnableStar = true
|
r := makeBaseEnhancedHarness(t)
|
||||||
|
fSys := filesys.MakeFsOnDisk()
|
||||||
|
r.Harness = MakeHarnessWithFs(t, fSys)
|
||||||
|
tmpDir, err := ioutil.TempDir("", "kust-testing-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
panic("test harness cannot make tmp dir: " + err.Error())
|
||||||
}
|
}
|
||||||
p := provider.NewDefaultDepProvider()
|
r.ldr, err = fLdr.NewLoader(fLdr.RestrictionRootOnly, tmpDir, fSys)
|
||||||
resourceFactory := p.GetResourceFactory()
|
if err != nil {
|
||||||
resmapFactory := resmap.NewFactory(
|
panic("test harness cannot make ldr at tmp dir: " + err.Error())
|
||||||
resourceFactory, p.GetConflictDetectorFactory())
|
}
|
||||||
|
r.shouldWipeLdrRoot = true
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
result := &HarnessEnhanced{
|
func makeBaseEnhancedHarness(t *testing.T) *HarnessEnhanced {
|
||||||
Harness: MakeHarness(t),
|
rf := resmap.NewFactory(
|
||||||
pte: pte,
|
provider.NewDefaultDepProvider().GetResourceFactory())
|
||||||
rf: resmapFactory,
|
return &HarnessEnhanced{
|
||||||
pl: pLdr.NewLoader(pc, resmapFactory)}
|
pte: newPluginTestEnv(t).set(),
|
||||||
|
rf: rf,
|
||||||
|
pl: pLdr.NewLoader(
|
||||||
|
types.EnabledPluginConfig(types.BploLoadFromFileSys),
|
||||||
|
rf,
|
||||||
|
// Plugin configs are always located on disk,
|
||||||
|
// regardless of the test harness's FS
|
||||||
|
filesys.MakeFsOnDisk())}
|
||||||
|
}
|
||||||
|
|
||||||
// Point the file loader to the root ('/') of the in-memory file system.
|
func (th *HarnessEnhanced) ErrIfNoHelm() error {
|
||||||
result.ResetLoaderRoot(filesys.Separator)
|
_, err := exec.LookPath(th.GetPluginConfig().HelmConfig.Command)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
func (th *HarnessEnhanced) GetRoot() string {
|
||||||
|
return th.ldr.Root()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (th *HarnessEnhanced) Reset() {
|
func (th *HarnessEnhanced) Reset() {
|
||||||
|
if th.shouldWipeLdrRoot {
|
||||||
|
if !strings.HasPrefix(th.ldr.Root(), os.TempDir()) {
|
||||||
|
// sanity check.
|
||||||
|
panic("something strange about th.ldr.Root() = " + th.ldr.Root())
|
||||||
|
}
|
||||||
|
os.RemoveAll(th.ldr.Root())
|
||||||
|
}
|
||||||
th.pte.reset()
|
th.pte.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (th *HarnessEnhanced) GetPluginConfig() *types.PluginConfig {
|
||||||
|
return th.pl.Config()
|
||||||
|
}
|
||||||
|
|
||||||
func (th *HarnessEnhanced) PrepBuiltin(k string) *HarnessEnhanced {
|
func (th *HarnessEnhanced) PrepBuiltin(k string) *HarnessEnhanced {
|
||||||
return th.BuildGoPlugin(konfig.BuiltinPluginPackage, "", k)
|
return th.BuildGoPlugin(konfig.BuiltinPluginPackage, "", k)
|
||||||
}
|
}
|
||||||
@@ -108,7 +150,7 @@ func (th *HarnessEnhanced) LoadAndRunGenerator(
|
|||||||
}
|
}
|
||||||
rm, err := g.Generate()
|
rm, err := g.Generate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
th.t.Fatalf("Err: %v", err)
|
th.t.Fatalf("generate err: %v", err)
|
||||||
}
|
}
|
||||||
rm.RemoveBuildAnnotations()
|
rm.RemoveBuildAnnotations()
|
||||||
return rm
|
return rm
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func (e *errUnableToFind) Error() string {
|
|||||||
m = append(m, "('"+p.Value+"'; "+p.Key+")")
|
m = append(m, "('"+p.Value+"'; "+p.Key+")")
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"unable to find plugin root - tried: %s", strings.Join(m, ", "))
|
"unable to find %s - tried: %s", e.what, strings.Join(m, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewErrUnableToFind(w string, a []Pair) *errUnableToFind {
|
func NewErrUnableToFind(w string, a []Pair) *errUnableToFind {
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FieldSpec completely specifies a kustomizable field in
|
// FieldSpec completely specifies a kustomizable field in a k8s API object.
|
||||||
// an unstructured representation of a k8s API object.
|
|
||||||
// It helps define the operands of transformations.
|
// It helps define the operands of transformations.
|
||||||
//
|
//
|
||||||
// For example, a directive to add a common label to objects
|
// For example, a directive to add a common label to objects
|
||||||
|
|||||||
@@ -3,14 +3,77 @@
|
|||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
// HelmChartArgs contains the metadata of how to generate a secret.
|
type HelmGlobals struct {
|
||||||
|
// ChartHome is a file path, relative to the kustomization root,
|
||||||
|
// to a directory containing a subdirectory for each chart to be
|
||||||
|
// included in the kustomization.
|
||||||
|
// The default value of this field is "charts".
|
||||||
|
// So, for example, kustomize looks for the minecraft chart
|
||||||
|
// at {kustomizationRoot}/{ChartHome}/minecraft.
|
||||||
|
// If the chart is there at build time, kustomize will use it as found,
|
||||||
|
// and not check version numbers or dates.
|
||||||
|
// If the chart is not there, kustomize will attempt to pull it
|
||||||
|
// using the version number specified in the kustomization file,
|
||||||
|
// and put it there. To suppress the pull attempt, simply assure
|
||||||
|
// that the chart is already there.
|
||||||
|
ChartHome string `json:"chartHome,omitempty" yaml:"chartHome,omitempty"`
|
||||||
|
|
||||||
|
// ConfigHome defines a value that kustomize should pass to helm via
|
||||||
|
// the HELM_CONFIG_HOME environment variable. kustomize doesn't attempt
|
||||||
|
// to read or write this directory.
|
||||||
|
// If omitted, {tmpDir}/helm is used, where {tmpDir} is some temporary
|
||||||
|
// directory created by kustomize for the benefit of helm.
|
||||||
|
// Likewise, kustomize sets
|
||||||
|
// HELM_CACHE_HOME={ConfigHome}/.cache
|
||||||
|
// HELM_DATA_HOME={ConfigHome}/.data
|
||||||
|
// for the helm subprocess.
|
||||||
|
ConfigHome string `json:"configHome,omitempty" yaml:"configHome,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelmChart struct {
|
||||||
|
// Name is the name of the chart, e.g. 'minecraft'.
|
||||||
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
|
|
||||||
|
// Version is the version of the chart, e.g. '3.1.3'
|
||||||
|
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
||||||
|
|
||||||
|
// Repo is a URL locating the chart on the internet.
|
||||||
|
// This is the argument to helm's `--repo` flag, e.g.
|
||||||
|
// `https://itzg.github.io/minecraft-server-charts`.
|
||||||
|
Repo string `json:"repo,omitempty" yaml:"repo,omitempty"`
|
||||||
|
|
||||||
|
// ReleaseName replaces RELEASE-NAME in chart template output,
|
||||||
|
// making a particular inflation of a chart unique with respect to
|
||||||
|
// other inflations of the same chart in a cluster. It's the first
|
||||||
|
// argument to the helm `install` and `template` commands, i.e.
|
||||||
|
// helm install {RELEASE-NAME} {chartName}
|
||||||
|
// helm template {RELEASE-NAME} {chartName}
|
||||||
|
// If omitted, the flag --generate-name is passed to 'helm template'.
|
||||||
|
ReleaseName string `json:"releaseName,omitempty" yaml:"releaseName,omitempty"`
|
||||||
|
|
||||||
|
// ValuesFile is local file path to a values file to use _instead of_
|
||||||
|
// the default values that accompanied the chart.
|
||||||
|
// The default values are in '{ChartHome}/{Name}/values.yaml'.
|
||||||
|
ValuesFile string `json:"valuesFile,omitempty" yaml:"valuesFile,omitempty"`
|
||||||
|
|
||||||
|
// ValuesInline holds value mappings specified directly,
|
||||||
|
// rather than in a separate file.
|
||||||
|
ValuesInline map[string]interface{} `json:"valuesInline,omitempty" yaml:"valuesInline,omitempty"`
|
||||||
|
|
||||||
|
// ValuesMerge specifies how to treat ValuesInline with respect to Values.
|
||||||
|
// Legal values: 'merge', 'override', 'replace'.
|
||||||
|
// Defaults to 'override'.
|
||||||
|
ValuesMerge string `json:"valuesMerge,omitempty" yaml:"valuesMerge,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelmChartArgs contains arguments to helm.
|
||||||
|
// Deprecated. Use HelmGlobals and HelmChart instead.
|
||||||
type HelmChartArgs struct {
|
type HelmChartArgs struct {
|
||||||
ChartName string `json:"chartName,omitempty" yaml:"chartName,omitempty"`
|
ChartName string `json:"chartName,omitempty" yaml:"chartName,omitempty"`
|
||||||
ChartVersion string `json:"chartVersion,omitempty" yaml:"chartVersion,omitempty"`
|
ChartVersion string `json:"chartVersion,omitempty" yaml:"chartVersion,omitempty"`
|
||||||
ChartRepoURL string `json:"chartRepoUrl,omitempty" yaml:"chartRepoUrl,omitempty"`
|
ChartRepoURL string `json:"chartRepoUrl,omitempty" yaml:"chartRepoUrl,omitempty"`
|
||||||
ChartHome string `json:"chartHome,omitempty" yaml:"chartHome,omitempty"`
|
ChartHome string `json:"chartHome,omitempty" yaml:"chartHome,omitempty"`
|
||||||
// Use chartRelease to keep compatible with old exec plugin
|
ChartRepoName string `json:"chartRepoName,omitempty" yaml:"chartRepoName,omitempty"`
|
||||||
ChartRepoName string `json:"chartRelease,omitempty" yaml:"chartRelease,omitempty"`
|
|
||||||
HelmBin string `json:"helmBin,omitempty" yaml:"helmBin,omitempty"`
|
HelmBin string `json:"helmBin,omitempty" yaml:"helmBin,omitempty"`
|
||||||
HelmHome string `json:"helmHome,omitempty" yaml:"helmHome,omitempty"`
|
HelmHome string `json:"helmHome,omitempty" yaml:"helmHome,omitempty"`
|
||||||
Values string `json:"values,omitempty" yaml:"values,omitempty"`
|
Values string `json:"values,omitempty" yaml:"values,omitempty"`
|
||||||
@@ -20,3 +83,32 @@ type HelmChartArgs struct {
|
|||||||
ReleaseNamespace string `json:"releaseNamespace,omitempty" yaml:"releaseNamespace,omitempty"`
|
ReleaseNamespace string `json:"releaseNamespace,omitempty" yaml:"releaseNamespace,omitempty"`
|
||||||
ExtraArgs []string `json:"extraArgs,omitempty" yaml:"extraArgs,omitempty"`
|
ExtraArgs []string `json:"extraArgs,omitempty" yaml:"extraArgs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SplitHelmParameters splits helm parameters into
|
||||||
|
// per-chart params and global chart-independent parameters.
|
||||||
|
func SplitHelmParameters(
|
||||||
|
oldArgs []HelmChartArgs) (charts []HelmChart, globals HelmGlobals) {
|
||||||
|
for _, old := range oldArgs {
|
||||||
|
charts = append(charts, makeHelmChartFromHca(&old))
|
||||||
|
if old.HelmHome != "" {
|
||||||
|
// last non-empty wins
|
||||||
|
globals.ConfigHome = old.HelmHome
|
||||||
|
}
|
||||||
|
if old.ChartHome != "" {
|
||||||
|
// last non-empty wins
|
||||||
|
globals.ChartHome = old.ChartHome
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return charts, globals
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeHelmChartFromHca(old *HelmChartArgs) (c HelmChart) {
|
||||||
|
c.Name = old.ChartName
|
||||||
|
c.Version = old.ChartVersion
|
||||||
|
c.Repo = old.ChartRepoURL
|
||||||
|
c.ValuesFile = old.Values
|
||||||
|
c.ValuesInline = old.ValuesLocal
|
||||||
|
c.ValuesMerge = old.ValuesMerge
|
||||||
|
c.ReleaseName = old.ReleaseName
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package types
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
@@ -46,6 +47,9 @@ type Kustomization struct {
|
|||||||
// CommonLabels to add to all objects and selectors.
|
// CommonLabels to add to all objects and selectors.
|
||||||
CommonLabels map[string]string `json:"commonLabels,omitempty" yaml:"commonLabels,omitempty"`
|
CommonLabels map[string]string `json:"commonLabels,omitempty" yaml:"commonLabels,omitempty"`
|
||||||
|
|
||||||
|
// Labels to add to all objects but not selectors.
|
||||||
|
Labels []Label `json:"labels,omitempty" yaml:"labels,omitempty"`
|
||||||
|
|
||||||
// CommonAnnotations to add to all objects.
|
// CommonAnnotations to add to all objects.
|
||||||
CommonAnnotations map[string]string `json:"commonAnnotations,omitempty" yaml:"commonAnnotations,omitempty"`
|
CommonAnnotations map[string]string `json:"commonAnnotations,omitempty" yaml:"commonAnnotations,omitempty"`
|
||||||
|
|
||||||
@@ -125,9 +129,14 @@ type Kustomization struct {
|
|||||||
// the map will have a suffix hash generated from its contents.
|
// the map will have a suffix hash generated from its contents.
|
||||||
SecretGenerator []SecretArgs `json:"secretGenerator,omitempty" yaml:"secretGenerator,omitempty"`
|
SecretGenerator []SecretArgs `json:"secretGenerator,omitempty" yaml:"secretGenerator,omitempty"`
|
||||||
|
|
||||||
|
// HelmGlobals contains helm configuration that isn't chart specific.
|
||||||
|
HelmGlobals *HelmGlobals `json:"helmGlobals,omitempty" yaml:"helmGlobals,omitempty"`
|
||||||
|
|
||||||
|
// HelmCharts is a list of helm chart configuration instances.
|
||||||
|
HelmCharts []HelmChart `json:"helmCharts,omitempty" yaml:"helmCharts,omitempty"`
|
||||||
|
|
||||||
// HelmChartInflationGenerator is a list of helm chart configurations.
|
// HelmChartInflationGenerator is a list of helm chart configurations.
|
||||||
// The resulting resource is a normal operand rendered from
|
// Deprecated. Auto-converted to HelmGlobals and HelmCharts.
|
||||||
// a remote chart by `helm template`
|
|
||||||
HelmChartInflationGenerator []HelmChartArgs `json:"helmChartInflationGenerator,omitempty" yaml:"helmChartInflationGenerator,omitempty"`
|
HelmChartInflationGenerator []HelmChartArgs `json:"helmChartInflationGenerator,omitempty" yaml:"helmChartInflationGenerator,omitempty"`
|
||||||
|
|
||||||
// GeneratorOptions modify behavior of all ConfigMap and Secret generators.
|
// GeneratorOptions modify behavior of all ConfigMap and Secret generators.
|
||||||
@@ -181,15 +190,42 @@ func (k *Kustomization) FixKustomizationPostUnmarshalling() {
|
|||||||
k.SecretGenerator[i].EnvSource = ""
|
k.SecretGenerator[i].EnvSource = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
charts, globals := SplitHelmParameters(k.HelmChartInflationGenerator)
|
||||||
|
if k.HelmGlobals == nil {
|
||||||
|
if globals.ChartHome != "" || globals.ConfigHome != "" {
|
||||||
|
k.HelmGlobals = &globals
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k.HelmCharts = append(k.HelmCharts, charts...)
|
||||||
|
// Wipe it for the fix command.
|
||||||
|
k.HelmChartInflationGenerator = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FixKustomizationPreMarshalling fixes things
|
// FixKustomizationPreMarshalling fixes things
|
||||||
// that should occur after the kustomization file
|
// that should occur after the kustomization file
|
||||||
// has been processed.
|
// has been processed.
|
||||||
func (k *Kustomization) FixKustomizationPreMarshalling() {
|
func (k *Kustomization) FixKustomizationPreMarshalling() error {
|
||||||
// PatchesJson6902 should be under the Patches field.
|
// PatchesJson6902 should be under the Patches field.
|
||||||
k.Patches = append(k.Patches, k.PatchesJson6902...)
|
k.Patches = append(k.Patches, k.PatchesJson6902...)
|
||||||
k.PatchesJson6902 = nil
|
k.PatchesJson6902 = nil
|
||||||
|
|
||||||
|
// this fix is not in FixKustomizationPostUnmarshalling because
|
||||||
|
// it will break some commands like `create` and `add`. those
|
||||||
|
// commands depend on 'commonLabels' field
|
||||||
|
if cl := labelFromCommonLabels(k.CommonLabels); cl != nil {
|
||||||
|
// check conflicts between commonLabels and labels
|
||||||
|
for _, l := range k.Labels {
|
||||||
|
for k := range l.Pairs {
|
||||||
|
if _, exist := cl.Pairs[k]; exist {
|
||||||
|
return fmt.Errorf("label name '%s' exists in both commonLabels and labels", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
k.Labels = append(k.Labels, *cl)
|
||||||
|
k.CommonLabels = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Kustomization) EnforceFields() []string {
|
func (k *Kustomization) EnforceFields() []string {
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ func TestFixKustomizationPostUnmarshalling(t *testing.T) {
|
|||||||
EnvSource: "c",
|
EnvSource: "c",
|
||||||
},
|
},
|
||||||
}}}
|
}}}
|
||||||
|
k.CommonLabels = map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
k.FixKustomizationPostUnmarshalling()
|
k.FixKustomizationPostUnmarshalling()
|
||||||
|
|
||||||
expected := Kustomization{
|
expected := Kustomization{
|
||||||
@@ -35,6 +38,9 @@ func TestFixKustomizationPostUnmarshalling(t *testing.T) {
|
|||||||
EnvSources: []string{"a", "b", "c"},
|
EnvSources: []string{"a", "b", "c"},
|
||||||
},
|
},
|
||||||
}}},
|
}}},
|
||||||
|
CommonLabels: map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(k, expected) {
|
if !reflect.DeepEqual(k, expected) {
|
||||||
t.Fatalf("unexpected output: %v", k)
|
t.Fatalf("unexpected output: %v", k)
|
||||||
|
|||||||
25
api/types/labels.go
Normal file
25
api/types/labels.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2019 The Kubernetes Authors.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
// Pairs contains the key-value pairs for labels to add
|
||||||
|
Pairs map[string]string `json:"pairs,omitempty" yaml:"pairs,omitempty"`
|
||||||
|
// IncludeSelectors inidicates should transformer include the
|
||||||
|
// fieldSpecs for selectors. Custom fieldSpecs specified by
|
||||||
|
// FieldSpecs will be merged with builtin fieldSpecs if this
|
||||||
|
// is true.
|
||||||
|
IncludeSelectors bool `json:"includeSelectors,omitempty" yaml:"includeSelectors,omitempty"`
|
||||||
|
FieldSpecs []FieldSpec `json:"fields,omitempty" yaml:"fields,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func labelFromCommonLabels(commonLabels map[string]string) *Label {
|
||||||
|
if len(commonLabels) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Label{
|
||||||
|
Pairs: commonLabels,
|
||||||
|
IncludeSelectors: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
// Patch represent either a Strategic Merge Patch or a JSON patch
|
// Patch represent either a Strategic Merge Patch or a JSON patch
|
||||||
// and its targets.
|
// and its targets.
|
||||||
// The content of the patch can either be from a file
|
// The content of the patch can either be from a file
|
||||||
@@ -16,6 +18,9 @@ type Patch struct {
|
|||||||
|
|
||||||
// Target points to the resources that the patch is applied to
|
// Target points to the resources that the patch is applied to
|
||||||
Target *Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
Target *Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
||||||
|
|
||||||
|
// Options is a list of options for the patch
|
||||||
|
Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equals return true if p equals o.
|
// Equals return true if p equals o.
|
||||||
@@ -24,5 +29,6 @@ func (p *Patch) Equals(o Patch) bool {
|
|||||||
(p.Target != nil && o.Target != nil && *p.Target == *o.Target)
|
(p.Target != nil && o.Target != nil && *p.Target == *o.Target)
|
||||||
return p.Path == o.Path &&
|
return p.Path == o.Path &&
|
||||||
p.Patch == o.Patch &&
|
p.Patch == o.Patch &&
|
||||||
targetEqual
|
targetEqual &&
|
||||||
|
reflect.DeepEqual(p.Options, o.Options)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,15 @@ import (
|
|||||||
|
|
||||||
func TestPatchEquals(t *testing.T) {
|
func TestPatchEquals(t *testing.T) {
|
||||||
selector := Selector{
|
selector := Selector{
|
||||||
Gvk: resid.Gvk{
|
KrmId: KrmId{
|
||||||
Group: "group",
|
Gvk: resid.Gvk{
|
||||||
Version: "version",
|
Group: "group",
|
||||||
Kind: "kind",
|
Version: "version",
|
||||||
|
Kind: "kind",
|
||||||
|
},
|
||||||
|
Name: "name",
|
||||||
|
Namespace: "namespace",
|
||||||
},
|
},
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
LabelSelector: "selector",
|
LabelSelector: "selector",
|
||||||
AnnotationSelector: "selector",
|
AnnotationSelector: "selector",
|
||||||
}
|
}
|
||||||
@@ -38,13 +40,15 @@ func TestPatchEquals(t *testing.T) {
|
|||||||
Path: "foo",
|
Path: "foo",
|
||||||
Patch: "bar",
|
Patch: "bar",
|
||||||
Target: &Selector{
|
Target: &Selector{
|
||||||
Gvk: resid.Gvk{
|
KrmId: KrmId{
|
||||||
Group: "group",
|
Gvk: resid.Gvk{
|
||||||
Version: "version",
|
Group: "group",
|
||||||
Kind: "kind",
|
Version: "version",
|
||||||
|
Kind: "kind",
|
||||||
|
},
|
||||||
|
Name: "name",
|
||||||
|
Namespace: "namespace",
|
||||||
},
|
},
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
LabelSelector: "selector",
|
LabelSelector: "selector",
|
||||||
AnnotationSelector: "selector",
|
AnnotationSelector: "selector",
|
||||||
},
|
},
|
||||||
@@ -53,13 +57,15 @@ func TestPatchEquals(t *testing.T) {
|
|||||||
Path: "foo",
|
Path: "foo",
|
||||||
Patch: "bar",
|
Patch: "bar",
|
||||||
Target: &Selector{
|
Target: &Selector{
|
||||||
Gvk: resid.Gvk{
|
KrmId: KrmId{
|
||||||
Group: "group",
|
Gvk: resid.Gvk{
|
||||||
Version: "version",
|
Group: "group",
|
||||||
Kind: "kind",
|
Version: "version",
|
||||||
|
Kind: "kind",
|
||||||
|
},
|
||||||
|
Name: "name",
|
||||||
|
Namespace: "namespace",
|
||||||
},
|
},
|
||||||
Name: "name",
|
|
||||||
Namespace: "namespace",
|
|
||||||
LabelSelector: "selector",
|
LabelSelector: "selector",
|
||||||
AnnotationSelector: "selector",
|
AnnotationSelector: "selector",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,27 +3,13 @@
|
|||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
type HelmConfig struct {
|
||||||
|
Enabled bool
|
||||||
|
Command string
|
||||||
|
}
|
||||||
|
|
||||||
// PluginConfig holds plugin configuration.
|
// PluginConfig holds plugin configuration.
|
||||||
type PluginConfig struct {
|
type PluginConfig struct {
|
||||||
// AbsPluginHome is the home of kustomize plugins.
|
|
||||||
// Kustomize plugin configuration files are k8s-style objects
|
|
||||||
// containing the fields 'apiVersion' and 'kind', e.g.
|
|
||||||
// apiVersion: apps/v1
|
|
||||||
// kind: Deployment
|
|
||||||
// kustomize reads plugin configuration data from a file path
|
|
||||||
// specified in the 'generators:' or 'transformers:' field of a
|
|
||||||
// kustomization file. kustomize must then use this data to both
|
|
||||||
// locate the plugin and configure it.
|
|
||||||
// Every kustomize plugin (its code, its tests, its supporting data
|
|
||||||
// files, etc.) must be housed in its own directory at
|
|
||||||
// ${AbsPluginHome}/${pluginApiVersion}/LOWERCASE(${pluginKind})
|
|
||||||
// where
|
|
||||||
// - ${AbsPluginHome} is an absolute path, defined below.
|
|
||||||
// - ${pluginApiVersion} is taken from the plugin config file.
|
|
||||||
// - ${pluginKind} is taken from the plugin config file.
|
|
||||||
// The value of AbsPluginHome can be any absolute path.
|
|
||||||
AbsPluginHome string
|
|
||||||
|
|
||||||
// PluginRestrictions distinguishes plugin restrictions.
|
// PluginRestrictions distinguishes plugin restrictions.
|
||||||
PluginRestrictions PluginRestrictions
|
PluginRestrictions PluginRestrictions
|
||||||
|
|
||||||
@@ -32,4 +18,30 @@ type PluginConfig struct {
|
|||||||
|
|
||||||
// FnpLoadingOptions sets the way function-based plugin behaviors.
|
// FnpLoadingOptions sets the way function-based plugin behaviors.
|
||||||
FnpLoadingOptions FnPluginLoadingOptions
|
FnpLoadingOptions FnPluginLoadingOptions
|
||||||
|
|
||||||
|
// HelmConfig contains metadata needed for allowing and running helm.
|
||||||
|
HelmConfig HelmConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func EnabledPluginConfig(b BuiltinPluginLoadingOptions) (pc *PluginConfig) {
|
||||||
|
pc = MakePluginConfig(PluginRestrictionsNone, b)
|
||||||
|
pc.FnpLoadingOptions.EnableStar = true
|
||||||
|
pc.HelmConfig.Enabled = true
|
||||||
|
// If this command is not on PATH, tests needing it should skip.
|
||||||
|
pc.HelmConfig.Command = "helmV3"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func DisabledPluginConfig() *PluginConfig {
|
||||||
|
return MakePluginConfig(
|
||||||
|
PluginRestrictionsBuiltinsOnly,
|
||||||
|
BploUseStaticallyLinked)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakePluginConfig(pr PluginRestrictions,
|
||||||
|
b BuiltinPluginLoadingOptions) *PluginConfig {
|
||||||
|
return &PluginConfig{
|
||||||
|
PluginRestrictions: pr,
|
||||||
|
BpLoadingOptions: b,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,59 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
// Copyright 2021 The Kubernetes Authors.
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
const DefaultReplacementFieldPath = "metadata.name"
|
||||||
|
|
||||||
// Replacement defines how to perform a substitution
|
// Replacement defines how to perform a substitution
|
||||||
// where it is from and where it is to.
|
// where it is from and where it is to.
|
||||||
type Replacement struct {
|
type Replacement struct {
|
||||||
Source *ReplSource `json:"source" yaml:"source"`
|
// The source of the value.
|
||||||
Target *ReplTarget `json:"target" yaml:"target"`
|
Source *SourceSelector `json:"source" yaml:"source"`
|
||||||
|
|
||||||
|
// The N fields to write the value to.
|
||||||
|
Targets []*TargetSelector `json:"targets" yaml:"targets"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplSource defines where a substitution is from
|
// SourceSelector is the source of the replacement transformer.
|
||||||
// It can from two different kinds of sources
|
type SourceSelector struct {
|
||||||
// - from a field of one resource
|
// A specific object to read it from.
|
||||||
// - from a string
|
KrmId `json:",inline,omitempty" yaml:",inline,omitempty"`
|
||||||
type ReplSource struct {
|
|
||||||
ObjRef *Target `json:"objref,omitempty" yaml:"objref,omitempty"`
|
// Structured field path expected in the allowed object.
|
||||||
FieldRef string `json:"fieldref,omitempty" yaml:"fiedldref,omitempty"`
|
FieldPath string `json:"fieldPath" yaml:"fieldPath"`
|
||||||
Value string `json:"value,omitempty" yaml:"value,omitempty"`
|
|
||||||
|
// Used to refine the interpretation of the field.
|
||||||
|
Options *FieldOptions `json:"options" yaml:"options"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplTarget defines where a substitution is to.
|
// TargetSelector specifies fields in one or more objects.
|
||||||
type ReplTarget struct {
|
type TargetSelector struct {
|
||||||
ObjRef *Selector `json:"objref,omitempty" yaml:"objref,omitempty"`
|
// Include objects that match this.
|
||||||
FieldRefs []string `json:"fieldrefs,omitempty" yaml:"fieldrefs,omitempty"`
|
Select *Selector `json:"select" yaml:"select"`
|
||||||
|
|
||||||
|
// From the allowed set, remove objects that match this.
|
||||||
|
Reject []*Selector `json:"reject" yaml:"reject"`
|
||||||
|
|
||||||
|
// Structured field paths expected in each allowed object.
|
||||||
|
FieldPaths []string `json:"fieldPaths" yaml:"fieldPaths"`
|
||||||
|
|
||||||
|
// Used to refine the interpretation of the field.
|
||||||
|
Options *FieldOptions `json:"options" yaml:"options"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldOptions refine the interpretation of FieldPaths.
|
||||||
|
type FieldOptions struct {
|
||||||
|
// Used to split/join the field.
|
||||||
|
Delimiter string `json:"delimiter" yaml:"delimiter"`
|
||||||
|
|
||||||
|
// Which position in the split to consider.
|
||||||
|
Index int `json:"index" yaml:"index"`
|
||||||
|
|
||||||
|
// TODO (#3492): Implement use of this option
|
||||||
|
// None, Base64, URL, Hex, etc
|
||||||
|
Encoding string `json:"encoding" yaml:"encoding"`
|
||||||
|
|
||||||
|
// If field missing, add it.
|
||||||
|
Create bool `json:"create" yaml:"create"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ import (
|
|||||||
// Any resource that matches intersection of all conditions
|
// Any resource that matches intersection of all conditions
|
||||||
// is included in this set.
|
// is included in this set.
|
||||||
type Selector struct {
|
type Selector struct {
|
||||||
resid.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
|
// KrmId refers to a GVKN/Ns of a resource.
|
||||||
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
KrmId `json:",inline,omitempty" yaml:",inline,omitempty"`
|
||||||
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
|
||||||
|
|
||||||
// AnnotationSelector is a string that follows the label selection expression
|
// AnnotationSelector is a string that follows the label selection expression
|
||||||
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api
|
// https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api
|
||||||
@@ -28,6 +27,23 @@ type Selector struct {
|
|||||||
LabelSelector string `json:"labelSelector,omitempty" yaml:"labelSelector,omitempty"`
|
LabelSelector string `json:"labelSelector,omitempty" yaml:"labelSelector,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KrmId refers to a GVKN/Ns of a resource.
|
||||||
|
type KrmId struct {
|
||||||
|
resid.Gvk `json:",inline,omitempty" yaml:",inline,omitempty"`
|
||||||
|
Name string `json:"name,omitempty" yaml:"name,omitempty"`
|
||||||
|
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match returns true if id selects other, i.e. id's fields
|
||||||
|
// either match other's or are empty
|
||||||
|
func (id *KrmId) Match(other *KrmId) bool {
|
||||||
|
return (id.Group == "" || id.Group == other.Group) &&
|
||||||
|
(id.Version == "" || id.Version == other.Version) &&
|
||||||
|
(id.Kind == "" || id.Kind == other.Kind) &&
|
||||||
|
(id.Name == "" || id.Name == other.Name) &&
|
||||||
|
(id.Namespace == "" || id.Namespace == other.Namespace)
|
||||||
|
}
|
||||||
|
|
||||||
// SelectorRegex is a Selector with regex in GVK
|
// SelectorRegex is a Selector with regex in GVK
|
||||||
// Any resource that matches intersection of all conditions
|
// Any resource that matches intersection of all conditions
|
||||||
// is included in this set.
|
// is included in this set.
|
||||||
|
|||||||
@@ -15,10 +15,12 @@ func TestSelectorRegexMatchGvk(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Gvk: resid.Gvk{
|
KrmId: KrmId{
|
||||||
Group: "group",
|
Gvk: resid.Gvk{
|
||||||
Version: "version",
|
Group: "group",
|
||||||
Kind: "kind",
|
Version: "version",
|
||||||
|
Kind: "kind",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
G: resid.Gvk{
|
G: resid.Gvk{
|
||||||
@@ -30,10 +32,12 @@ func TestSelectorRegexMatchGvk(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Gvk: resid.Gvk{
|
KrmId: KrmId{
|
||||||
Group: "group",
|
Gvk: resid.Gvk{
|
||||||
Version: "",
|
Group: "group",
|
||||||
Kind: "",
|
Version: "",
|
||||||
|
Kind: "",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
G: resid.Gvk{
|
G: resid.Gvk{
|
||||||
@@ -45,10 +49,12 @@ func TestSelectorRegexMatchGvk(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Gvk: resid.Gvk{
|
KrmId: KrmId{
|
||||||
Group: "group",
|
Gvk: resid.Gvk{
|
||||||
Version: "version",
|
Group: "group",
|
||||||
Kind: "kind",
|
Version: "version",
|
||||||
|
Kind: "kind",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
G: resid.Gvk{
|
G: resid.Gvk{
|
||||||
@@ -60,10 +66,12 @@ func TestSelectorRegexMatchGvk(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Gvk: resid.Gvk{
|
KrmId: KrmId{
|
||||||
Group: "group",
|
Gvk: resid.Gvk{
|
||||||
Version: "version",
|
Group: "group",
|
||||||
Kind: "kind",
|
Version: "version",
|
||||||
|
Kind: "kind",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
G: resid.Gvk{
|
G: resid.Gvk{
|
||||||
@@ -75,10 +83,12 @@ func TestSelectorRegexMatchGvk(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Gvk: resid.Gvk{
|
KrmId: KrmId{
|
||||||
Group: "g.*",
|
Gvk: resid.Gvk{
|
||||||
Version: "\\d+",
|
Group: "g.*",
|
||||||
Kind: ".{4}",
|
Version: "\\d+",
|
||||||
|
Kind: ".{4}",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
G: resid.Gvk{
|
G: resid.Gvk{
|
||||||
@@ -90,10 +100,12 @@ func TestSelectorRegexMatchGvk(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Gvk: resid.Gvk{
|
KrmId: KrmId{
|
||||||
Group: "g.*",
|
Gvk: resid.Gvk{
|
||||||
Version: "\\d+",
|
Group: "g.*",
|
||||||
Kind: ".{4}",
|
Version: "\\d+",
|
||||||
|
Kind: ".{4}",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
G: resid.Gvk{
|
G: resid.Gvk{
|
||||||
@@ -125,30 +137,38 @@ func TestSelectorRegexMatchName(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Name: "foo",
|
KrmId: KrmId{
|
||||||
Namespace: "bar",
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Expected: true,
|
Expected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Name: "foo",
|
KrmId: KrmId{
|
||||||
Namespace: "bar",
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Name: "bar",
|
Name: "bar",
|
||||||
Expected: false,
|
Expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Name: "f.*",
|
KrmId: KrmId{
|
||||||
|
Name: "f.*",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Expected: true,
|
Expected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Name: "b.*",
|
KrmId: KrmId{
|
||||||
|
Name: "b.*",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
Expected: false,
|
Expected: false,
|
||||||
@@ -174,30 +194,38 @@ func TestSelectorRegexMatchNamespace(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Name: "bar",
|
KrmId: KrmId{
|
||||||
Namespace: "foo",
|
Name: "bar",
|
||||||
|
Namespace: "foo",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Namespace: "foo",
|
Namespace: "foo",
|
||||||
Expected: true,
|
Expected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Name: "foo",
|
KrmId: KrmId{
|
||||||
Namespace: "bar",
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Namespace: "foo",
|
Namespace: "foo",
|
||||||
Expected: false,
|
Expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Namespace: "f.*",
|
KrmId: KrmId{
|
||||||
|
Namespace: "f.*",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Namespace: "foo",
|
Namespace: "foo",
|
||||||
Expected: true,
|
Expected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
S: Selector{
|
S: Selector{
|
||||||
Namespace: "b.*",
|
KrmId: KrmId{
|
||||||
|
Namespace: "b.*",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Namespace: "foo",
|
Namespace: "foo",
|
||||||
Expected: false,
|
Expected: false,
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/api/resid"
|
"sigs.k8s.io/kustomize/api/resid"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultFieldPath = "metadata.name"
|
|
||||||
|
|
||||||
// Var represents a variable whose value will be sourced
|
// Var represents a variable whose value will be sourced
|
||||||
// from a field in a Kubernetes object.
|
// from a field in a Kubernetes object.
|
||||||
type Var struct {
|
type Var struct {
|
||||||
@@ -71,7 +69,7 @@ type FieldSelector struct {
|
|||||||
// defaulting sets reference to field used by default.
|
// defaulting sets reference to field used by default.
|
||||||
func (v *Var) Defaulting() {
|
func (v *Var) Defaulting() {
|
||||||
if v.FieldRef.FieldPath == "" {
|
if v.FieldRef.FieldPath == "" {
|
||||||
v.FieldRef.FieldPath = defaultFieldPath
|
v.FieldRef.FieldPath = DefaultReplacementFieldPath
|
||||||
}
|
}
|
||||||
v.ObjRef.GVK()
|
v.ObjRef.GVK()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ func TestDefaulting(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
v.Defaulting()
|
v.Defaulting()
|
||||||
if v.FieldRef.FieldPath != defaultFieldPath {
|
if v.FieldRef.FieldPath != DefaultReplacementFieldPath {
|
||||||
t.Fatalf("expected %s, got %v",
|
t.Fatalf("expected %s, got %v",
|
||||||
defaultFieldPath, v.FieldRef.FieldPath)
|
DefaultReplacementFieldPath, v.FieldRef.FieldPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +127,7 @@ func TestVarSet(t *testing.T) {
|
|||||||
t.Fatalf("expected var")
|
t.Fatalf("expected var")
|
||||||
}
|
}
|
||||||
// Confirm defaulting.
|
// Confirm defaulting.
|
||||||
if v.FieldRef.FieldPath != defaultFieldPath {
|
if v.FieldRef.FieldPath != DefaultReplacementFieldPath {
|
||||||
t.Fatalf("unexpected field path: %v", v.FieldRef.FieldPath)
|
t.Fatalf("unexpected field path: %v", v.FieldRef.FieldPath)
|
||||||
}
|
}
|
||||||
// Confirm sorting.
|
// Confirm sorting.
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
.PHONY: generate license fix vet fmt test build tidy clean
|
.PHONY: generate license fix vet fmt test build tidy clean
|
||||||
|
|
||||||
GOBIN := $(shell go env GOPATH)/bin
|
GOBIN = $(shell go env GOBIN)
|
||||||
|
ifeq ($(GOBIN),)
|
||||||
|
GOBIN = $(shell go env GOPATH)/bin
|
||||||
|
endif
|
||||||
|
|
||||||
$(GOBIN)/addlicense:
|
$(GOBIN)/addlicense:
|
||||||
go get github.com/google/addlicense
|
go get github.com/google/addlicense
|
||||||
|
|||||||
@@ -16,5 +16,5 @@ require (
|
|||||||
golang.org/x/text v0.3.4 // indirect
|
golang.org/x/text v0.3.4 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1
|
gopkg.in/inf.v0 v0.9.1
|
||||||
sigs.k8s.io/kustomize/kyaml v0.10.15
|
sigs.k8s.io/kustomize/kyaml v0.10.17
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -290,5 +290,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
sigs.k8s.io/kustomize/kyaml v0.10.15 h1:dSLgG78KyaxN4HylPXdK+7zB3k7sW6q3IcCmcfKA+aI=
|
sigs.k8s.io/kustomize/kyaml v0.10.17 h1:4zrV0ym5AYa0e512q7K3Wp1u7mzoWW0xR3UHJcGWGIg=
|
||||||
sigs.k8s.io/kustomize/kyaml v0.10.15/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
|
sigs.k8s.io/kustomize/kyaml v0.10.17/go.mod h1:mlQFagmkm1P+W4lZJbJ/yaxMd8PqMRSC4cPcfUVt5Hg=
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user