Compare commits

...

61 Commits

Author SHA1 Message Date
Katrina Verey
b8ffc725c7 Update kustomize-in-kubectl chart for 1.22 2021-07-26 13:23:45 -07:00
Jeff Regan
76f1411922 Merge pull request #4082 from monopole/autoChanges
Automated go.sum and fmt changes under go 1.16.6
2021-07-24 09:42:29 -07:00
monopole
d1003d6f8f Automated changes under go 1.16.6 2021-07-24 09:18:58 -07:00
Natasha Sarkar
91f74e8d16 replace Resource.options with annotations (#4061) 2021-07-23 18:19:05 -07:00
Kubernetes Prow Robot
94c5096a95 Merge pull request #4048 from natasha41575/DeprecateCfgCmds
Deprecate some cfg commands
2021-07-21 18:15:51 -07:00
Kubernetes Prow Robot
f35aeb6a8e Merge pull request #4077 from abutcher/cdpath
Unset CDPATH in hack/install_kustomize.sh
2021-07-21 10:14:07 -07:00
Kubernetes Prow Robot
d6ce846047 Merge pull request #4076 from KnVerey/kyaml_fixture_fix
Make UpdateExpectedFromActual work with hierarchical testdata directories
2021-07-21 08:52:08 -07:00
Andrew Butcher
ec069e4f19 Unset CDPATH to restore default cd behavior. 2021-07-21 11:38:44 -04:00
Katrina Verey
c5adafd9ce Make UpdateExpectedFromActual work with hierarchical testdata directories 2021-07-20 18:13:04 -07:00
Natasha Sarkar
16dcc98cff deprecate some cfg commands 2021-07-19 14:31:20 -07:00
Kubernetes Prow Robot
59c410a70a Merge pull request #4063 from dosmanak/mdrip_MYGOBIN
fix: Allow custom MYGOBIN in mdrip invocations
2021-07-19 10:30:52 -07:00
Kubernetes Prow Robot
35d1c3f9b4 Merge pull request #4072 from natasha41575/Revert
revert 'fix kyaml issue with multiline scalars'
2021-07-15 14:37:02 -07:00
Natasha Sarkar
e17785af21 revert 'fix kyaml issue with multiline scalars' 2021-07-15 14:24:39 -07:00
Natasha Sarkar
0537b59f27 support yaml formatted openapi schema (#4017)
* support yaml formatted openapi schema

* suggested changes
2021-07-15 14:11:02 -07:00
Kubernetes Prow Robot
339e33d2f3 Merge pull request #4071 from KnVerey/update-yaml-script
Update fork updater script
2021-07-15 10:06:45 -07:00
Natasha Sarkar
f082ac02cf fix multiline scalar value issue 2021-07-15 08:41:03 -07:00
Katrina Verey
9538ae1258 Update fork updater script 2021-07-15 08:31:32 -07:00
Kubernetes Prow Robot
34981b664f Merge pull request #4069 from natasha41575/LineBreakIssue
fix kyaml issue with multiline scalars
2021-07-14 16:20:47 -07:00
Natasha Sarkar
477d8930e0 fix kyaml issue with multiline scalars 2021-07-14 15:19:21 -07:00
Kubernetes Prow Robot
b5091a566a Merge pull request #4067 from natasha41575/demonstrateLineBreakIssue
demonstrate line break preservation issue in kyaml
2021-07-14 11:52:18 -07:00
Natasha Sarkar
9981c45554 demonstrate line break preservation issue in kyaml 2021-07-14 11:39:14 -07:00
phani
0f736ec7fd Handle comments for seq indent derivation (#4064)
* Handle comments for seq indent derivation

* Suggested changes
2021-07-13 17:46:26 -07:00
Kubernetes Prow Robot
7826ad1e06 Merge pull request #4031 from rjferguson21/prefix-overlay-fail
Add failing test for replacements when using an overlay with a namePrefix
2021-07-13 14:42:01 -07:00
Frank Farzan
f4e6816338 Expand documentation of annotations used in manifests and KRM functions API (#3995)
* Expand documentation of annotations used in manifests and KRM function wire format.

- Reserve `internal.config.kubernetes.io` for control annotations
- Document `local-config` annotation in a seperate document (It's
  orthogonal to KRM functions).
- There is a internal annotation that uses `config.k8s.io` instead of
  `config.kubernetes.io` used by other annotations. See [1] and [2]. We
  should avoid using two seperate annotation prefixes and audit the
  codebase for any other annotation. Given the `id` control annotation is used
  for comment preservation (no existing function should be modifying
  it), I suggest moving this over to use
  `fn-ctrl.config.kubernets.io/id`.

[1]: 7e8ba62e9f/kyaml/fn/runtime/runtimeutil/runtimeutil.go (L195)
[2]: https://github.com/kubernetes-sigs/kustomize/pull/2465

* Move path/index annotation to use internal prefix

* Clarify MUST NOT vs SHOULD NOT for internal annotations

* Update cmd/config/docs/api-conventions/functions-spec.md

Co-authored-by: Katrina Verey <kn.verey@gmail.com>

* Update cmd/config/docs/api-conventions/functions-spec.md

Co-authored-by: Katrina Verey <kn.verey@gmail.com>

* Update cmd/config/docs/api-conventions/manifest-annotations.md

Co-authored-by: Katrina Verey <kn.verey@gmail.com>

* Remove kusotmization as example

Co-authored-by: Katrina Verey <kn.verey@gmail.com>
2021-07-13 11:58:00 -07:00
Kubernetes Prow Robot
4a13725678 Merge pull request #4043 from phanimarupaka/PreserveIndentation
Make seq indent configurable and add retain seq indent functionality
2021-07-13 10:24:30 -07:00
Petr Studeny
ab9b010856 fix: Allow custom MYGOBIN in mdrip invokations 2021-07-13 14:34:50 +02:00
Phani Teja Marupaka
29be7fabe4 Suggested changes 2021-07-12 23:34:03 -07:00
Phani Teja Marupaka
74e867833a First sequence indent wins 2021-07-09 14:10:30 -07:00
Rob Ferguson
91dc6d2a0f add additional replacement transformer test with todos for failures 2021-07-09 12:45:47 -05:00
Rob Ferguson
3c1fd0e9cf add replacement test failures 2021-07-09 10:39:38 -05:00
Rob Ferguson
4deeb7d59b make replacement transformer test pass and add todo 2021-07-09 09:49:08 -05:00
Phani Teja Marupaka
89b12cfc62 Change annotation name, error if conflicting options 2021-07-09 01:32:48 -07:00
Phani Teja Marupaka
c07ffa5c1e Update comments, tests, not expose indent option 2021-07-09 00:17:09 -07:00
Kubernetes Prow Robot
259fcfcef8 Merge pull request #4002 from natasha41575/resref
replace Resource.refBy with annotations
2021-07-08 17:26:54 -07:00
Phani Teja Marupaka
f81201b74d Add options, keep seqindent annotation equivalent to index annotation 2021-07-08 14:54:23 -07:00
Phani Teja Marupaka
6dbc74b32e Suggested changes 2021-07-07 23:22:15 -07:00
Phani Teja Marupaka
ed38b5fe2b Make seq indent configurable and add retain seq indent functionality 2021-07-07 16:53:40 -07:00
Natasha Sarkar
a84badb834 replace Resource.refBy with annotations 2021-07-07 15:22:36 -07:00
phani
e1804cbc76 Retain field order after running any arbitrary functions on resources (#4021)
* Reorder resource fields

* Fix comment conflict

* Update e2e test ordering

* Suggested changes
2021-07-07 10:12:44 -07:00
Kubernetes Prow Robot
d13eef7951 Merge pull request #4040 from zhouhaibing089/fix-iampolicy-mod
iampolicygenerator: update module name
2021-07-07 09:46:44 -07:00
Kubernetes Prow Robot
0b4c6baf44 Merge pull request #4039 from zhouhaibing089/handle-dot-git-suffix
api/internal/git: handle .git suffix in repospec
2021-07-07 09:24:43 -07:00
Haibing Zhou
b3af54340c iampolicygenerator: update module name
The previous module name is incorrect and seems to be copied from
another builtin plugin. This change fixes the name.
2021-07-05 14:11:32 -07:00
Haibing Zhou
8c14b9d1af api/internal/git: handle .git suffix in repospec
This change adds a new test case for parsing url with `.git` suffix. In
that case, we should have the full url as clone spec with an empty
abspath.
2021-07-05 13:10:35 -07:00
Natasha Sarkar
d818ccae92 Merge pull request #4037 from natasha41575/pin
Unpin modules
2021-07-02 14:25:03 -07:00
Natasha Sarkar
4cea8b9785 Unpin modules 2021-07-02 14:10:05 -07:00
Kubernetes Prow Robot
84a36801e0 Merge pull request #4036 from joebowbeer/patch-1
Fix broken KEP link in README
2021-07-02 13:58:10 -07:00
Joe Bowbeer
6eb7b3508d Fix broken KEP link in README 2021-07-02 13:12:22 -07:00
Rob Ferguson
2a5f4ac7d7 add failing test for replacements 2021-07-02 08:54:52 -05:00
Kubernetes Prow Robot
518a16d3ac Merge pull request #4014 from brianpursley/doc-separator
Updated ByteReader to allow white space and comments content after --- when splitting YAML documents
2021-07-01 14:25:51 -07:00
Jeff Regan
d53a2ad45d Merge pull request #4027 from monopole/deleteWindowsBuild
Temporarily remove windows build.
2021-06-30 15:37:56 -07:00
monopole
bb02a7645b Temporarily remove windows build. 2021-06-30 15:36:57 -07:00
Jeff Regan
5a9d90c872 Merge pull request #4026 from monopole/upgradeGoReleaser
release process: upgrade to goreleaser v0.172.1 calling go1.16.5
2021-06-30 14:04:18 -07:00
monopole
4fd7269643 release process: upgrade to goreleaser v0.172.1 calling go go1.16.5 2021-06-30 14:02:55 -07:00
Kubernetes Prow Robot
1eb3c1a075 Merge pull request #4025 from KnVerey/pinToApi
Pin to api v0.8.11
2021-06-30 12:53:03 -07:00
Katrina Verey
a1746f2f8c Pin to api v0.8.11 2021-06-30 12:42:04 -07:00
Kubernetes Prow Robot
b727febd08 Merge pull request #4023 from mengqiy/jsontag
add missing json tags
2021-06-30 12:37:03 -07:00
Natasha Sarkar
c819d69ae4 Merge pull request #4022 from KnVerey/pinToCmdConfig
Pin to cmd/config v0.9.13
2021-06-30 12:26:40 -07:00
Katrina Verey
bb6f83fb96 Pin to cmd/config v0.9.13 2021-06-30 12:16:04 -07:00
Mengqi Yu
02d14d724a add missing json tags 2021-06-30 12:15:50 -07:00
Katrina Verey
aa92d83d8c Pin to kyaml (#4020)
* Pin to kyaml

* Pin prefixsuffixtransformer to kyaml 0.11.0

* Pin replicacounttransformer to kyaml 0.11.0
2021-06-30 11:59:03 -07:00
brianpursley
78737f5a38 Updated ByteReader to allow white space and comments on the same line after --- and throw an error if anything else is detected 2021-06-24 21:32:39 -04:00
85 changed files with 2580 additions and 326 deletions

View File

@@ -32,6 +32,7 @@ will be reflected in the Kubernetes release notes.
| < v1.14 | n/a |
| v1.14-v1.20 | v2.0.3 |
| v1.21 | v4.0.5 |
| v1.22 | v4.2.0 |
[v2.0.3]: /../../tree/v2.0.3
[#2506]: https://github.com/kubernetes-sigs/kustomize/issues/2506
@@ -151,7 +152,7 @@ is governed by the [Kubernetes Code of Conduct].
[`make`]: https://www.gnu.org/software/make
[`sed`]: https://www.gnu.org/software/sed
[DAM]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#declarative-application-management
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/0008-kustomize.md
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/2377-Kustomize/README.md
[Kubernetes Code of Conduct]: code-of-conduct.md
[applied]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#apply
[base]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#base

View File

@@ -1514,9 +1514,9 @@ kind: Deployment
metadata:
name: pre-deploy
annotations:
config.kubernetes.io/previousNames: deploy,deploy
config.kubernetes.io/previousKinds: CronJob,Deployment
config.kubernetes.io/previousNamespaces: default,default
internal.config.kubernetes.io/previousNames: deploy,deploy
internal.config.kubernetes.io/previousKinds: CronJob,Deployment
internal.config.kubernetes.io/previousNamespaces: default,default
spec:
template:
spec:
@@ -1543,9 +1543,9 @@ kind: Deployment
metadata:
name: pre-deploy
annotations:
config.kubernetes.io/previousNames: deploy,deploy
config.kubernetes.io/previousKinds: CronJob,Deployment
config.kubernetes.io/previousNamespaces: default,default
internal.config.kubernetes.io/previousNames: deploy,deploy
internal.config.kubernetes.io/previousKinds: CronJob,Deployment
internal.config.kubernetes.io/previousNamespaces: default,default
spec:
template:
spec:

View File

@@ -11,7 +11,7 @@ require (
github.com/stretchr/testify v1.5.1
gopkg.in/yaml.v2 v2.4.0
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e
sigs.k8s.io/kustomize/kyaml v0.10.20
sigs.k8s.io/kustomize/kyaml v0.11.0
sigs.k8s.io/yaml v1.2.0
)

View File

@@ -897,7 +897,9 @@ func TestNameReferenceClusterWide(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
expected.RemoveBuildAnnotations()
m.RemoveBuildAnnotations()
if err = expected.ErrorIfNotEqualLists(m); err != nil {
t.Fatalf(notEqualErrFmt, err)
}

View File

@@ -362,10 +362,10 @@ func TestResolveVarsWithNoambiguation(t *testing.T) {
"metadata": map[string]interface{}{
"name": "sub-backendOne",
"annotations": map[string]interface{}{
"config.kubernetes.io/previousKinds": "Service",
"config.kubernetes.io/previousNames": "backendOne",
"config.kubernetes.io/previousNamespaces": "default",
"config.kubernetes.io/prefixes": "sub-",
"internal.config.kubernetes.io/previousKinds": "Service",
"internal.config.kubernetes.io/previousNames": "backendOne",
"internal.config.kubernetes.io/previousNamespaces": "default",
"internal.config.kubernetes.io/prefixes": "sub-",
},
}}).ResMap()

View File

@@ -125,7 +125,7 @@ func parseGitUrl(n string) (
index := strings.Index(n, gitSuffix)
orgRepo = n[0:index]
n = n[index+len(gitSuffix):]
if n[0] == '/' {
if len(n) > 0 && n[0] == '/' {
n = n[1:]
}
path, gitRef, gitTimeout, gitSubmodules = peelQuery(n)

View File

@@ -182,6 +182,12 @@ func TestNewRepoSpecFromUrl_CloneSpecs(t *testing.T) {
absPath: notCloned.String(),
ref: "",
},
"t12": {
input: "https://bitbucket.example.com/scm/project/repository.git",
cloneSpec: "https://bitbucket.example.com/scm/project/repository.git",
absPath: notCloned.String(),
ref: "",
},
}
for tn, tc := range testcases {
t.Run(tn, func(t *testing.T) {

View File

@@ -255,5 +255,5 @@ metadata:
actual.RemoveBuildAnnotations()
actYaml, err := actual.AsYaml()
assert.NoError(t, err)
assert.Equal(t, expYaml, actYaml)
assert.Equal(t, string(expYaml), string(actYaml))
}

View File

@@ -12,9 +12,11 @@ import (
const (
BuildAnnotationPreviousKinds = konfig.ConfigAnnoDomain + "/previousKinds"
BuildAnnotationPreviousNames = konfig.ConfigAnnoDomain + "/previousNames"
BuildAnnotationPreviousNamespaces = konfig.ConfigAnnoDomain + "/previousNamespaces"
BuildAnnotationPrefixes = konfig.ConfigAnnoDomain + "/prefixes"
BuildAnnotationSuffixes = konfig.ConfigAnnoDomain + "/suffixes"
BuildAnnotationPreviousNamespaces = konfig.ConfigAnnoDomain + "/previousNamespaces"
BuildAnnotationsRefBy = konfig.ConfigAnnoDomain + "/refBy"
BuildAnnotationsGenOptions = konfig.ConfigAnnoDomain + "/generatorOptions"
// the following are only for patches, to specify whether they can change names
// and kinds of their targets

View File

@@ -31,11 +31,12 @@ const (
// A program name, for use in help, finding the XDG_CONFIG_DIR, etc.
ProgramName = "kustomize"
// ConfigAnnoDomain is configuration-related annotation namespace.
ConfigAnnoDomain = "config.kubernetes.io"
// ConfigAnnoDomain is internal configuration-related annotation namespace.
// See https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md.
ConfigAnnoDomain = "internal.config.kubernetes.io"
// If a resource has this annotation, kustomize will drop it.
IgnoredByKustomizeAnnotation = ConfigAnnoDomain + "/local-config"
IgnoredByKustomizeAnnotation = "config.kubernetes.io/local-config"
// Label key that indicates the resources are built from Kustomize
ManagedbyLabelKey = "app.kubernetes.io/managed-by"

View File

@@ -17,6 +17,11 @@ func writeTestSchema(th kusttest_test.Harness, filepath string) {
th.WriteF(filepath+"mycrd_schema.json", string(bytes))
}
func writeTestSchemaYaml(th kusttest_test.Harness, filepath string) {
bytes, _ := ioutil.ReadFile("testdata/customschema.yaml")
th.WriteF(filepath+"mycrd_schema.yaml", string(bytes))
}
func writeCustomResource(th kusttest_test.Harness, filepath string) {
th.WriteF(filepath, `
apiVersion: example.com/v1alpha1
@@ -103,6 +108,21 @@ openapi:
th.AssertActualEqualsExpected(m, patchedCustomResource)
}
func TestCustomOpenApiFieldYaml(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `
resources:
- mycrd.yaml
openapi:
path: mycrd_schema.yaml
`+customSchemaPatch)
writeCustomResource(th, "mycrd.yaml")
writeTestSchemaYaml(th, "./")
openapi.ResetOpenAPI()
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, patchedCustomResource)
}
// Error if user tries to specify both builtin version
// and custom schema
func TestCustomOpenApiFieldBothPathAndVersion(t *testing.T) {

View File

@@ -342,3 +342,143 @@ spec:
name: nginx
`)
}
// TODO: Address namePrefix in overlay not applying to replacement targets
// The property `data.blue-name` should end up being `overlay-blue` instead of `blue`
// https://github.com/kubernetes-sigs/kustomize/issues/4034
func TestReplacementTransformerWithNamePrefixOverlay(t *testing.T) {
th := kusttest_test.MakeEnhancedHarness(t)
defer th.Reset()
th.WriteK("base", `
generatorOptions:
disableNameSuffixHash: true
configMapGenerator:
- name: blue
- name: red
replacements:
- source:
kind: ConfigMap
name: blue
fieldPath: metadata.name
targets:
- select:
name: red
fieldPaths:
- data.blue-name
options:
create: true
`)
th.WriteK(".", `
namePrefix: overlay-
resources:
- base
`)
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: ConfigMap
metadata:
name: overlay-blue
---
apiVersion: v1
data:
blue-name: blue
kind: ConfigMap
metadata:
name: overlay-red
`)
}
// TODO: Address namespace in overlay not applying to replacement targets
// The property `data.blue-namespace` should end up being `overlay-namespace` instead of `base-namespace`
// https://github.com/kubernetes-sigs/kustomize/issues/4034
func TestReplacementTransformerWithNamespaceOverlay(t *testing.T) {
th := kusttest_test.MakeEnhancedHarness(t)
defer th.Reset()
th.WriteK("base", `
namespace: base-namespace
generatorOptions:
disableNameSuffixHash: true
configMapGenerator:
- name: blue
- name: red
replacements:
- source:
kind: ConfigMap
name: blue
fieldPath: metadata.namespace
targets:
- select:
name: red
fieldPaths:
- data.blue-namespace
options:
create: true
`)
th.WriteK(".", `
namespace: overlay-namespace
resources:
- base
`)
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: ConfigMap
metadata:
name: blue
namespace: overlay-namespace
---
apiVersion: v1
data:
blue-namespace: base-namespace
kind: ConfigMap
metadata:
name: red
namespace: overlay-namespace
`)
}
// TODO: Address configMapGenerator suffix not applying to replacement targets
// The property `data.blue-name` should end up being `blue-6ct58987ht` instead of `blue`
// https://github.com/kubernetes-sigs/kustomize/issues/4034
func TestReplacementTransformerWithConfigMapGenerator(t *testing.T) {
th := kusttest_test.MakeEnhancedHarness(t)
defer th.Reset()
th.WriteK(".", `
configMapGenerator:
- name: blue
- name: red
replacements:
- source:
kind: ConfigMap
name: blue
fieldPath: metadata.name
targets:
- select:
name: red
fieldPaths:
- data.blue-name
options:
create: true
`)
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: ConfigMap
metadata:
name: blue-6ct58987ht
---
apiVersion: v1
data:
blue-name: blue
kind: ConfigMap
metadata:
name: red-dc6gc5btkc
`)
}

75
api/krusty/testdata/customschema.yaml vendored Normal file
View File

@@ -0,0 +1,75 @@
definitions:
v1alpha1.MyCRD:
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
spec:
properties:
template:
"$ref": "#/definitions/io.k8s.api.core.v1.PodTemplateSpec"
type: object
status:
properties:
success:
type: boolean
type: object
type: object
x-kubernetes-group-version-kind:
- group: example.com
kind: MyCRD
version: v1alpha1
io.k8s.api.core.v1.PodTemplateSpec:
properties:
metadata:
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"
spec:
"$ref": "#/definitions/io.k8s.api.core.v1.PodSpec"
type: object
io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta:
properties:
name:
type: string
type: object
io.k8s.api.core.v1.PodSpec:
properties:
containers:
items:
"$ref": "#/definitions/io.k8s.api.core.v1.Container"
type: array
x-kubernetes-patch-merge-key: name
x-kubernetes-patch-strategy: merge
type: object
io.k8s.api.core.v1.Container:
properties:
command:
items:
type: string
type: array
image:
type: string
name:
type: string
ports:
items:
"$ref": "#/definitions/io.k8s.api.core.v1.ContainerPort"
type: array
x-kubernetes-list-map-keys:
- containerPort
- protocol
x-kubernetes-list-type: map
x-kubernetes-patch-merge-key: containerPort
x-kubernetes-patch-strategy: merge
type: object
io.k8s.api.core.v1.ContainerPort:
properties:
containerPort:
type: integer
name:
type: string
protocol:
type: string
type: object

View File

@@ -219,8 +219,10 @@ BAR=baz
}
r, err := rmF.NewResMapFromConfigMapArgs(kvLdr, tc.input)
assert.NoError(t, err, tc.description)
r.RemoveBuildAnnotations()
rYaml, err := r.AsYaml()
assert.NoError(t, err, tc.description)
tc.expected.RemoveBuildAnnotations()
expYaml, err := tc.expected.AsYaml()
assert.NoError(t, err, tc.description)
assert.Equal(t, expYaml, rYaml)
@@ -252,6 +254,7 @@ func TestNewResMapFromSecretArgs(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
actual.RemoveBuildAnnotations()
actYaml, err := actual.AsYaml()
assert.NoError(t, err)

View File

@@ -343,9 +343,9 @@ func TestGetMatchingResourcesByAnyId(t *testing.T) {
"metadata": map[string]interface{}{
"name": "new-alice",
"annotations": map[string]interface{}{
"config.kubernetes.io/previousKinds": "ConfigMap",
"config.kubernetes.io/previousNames": "alice",
"config.kubernetes.io/previousNamespaces": "default",
"internal.config.kubernetes.io/previousKinds": "ConfigMap",
"internal.config.kubernetes.io/previousNames": "alice",
"internal.config.kubernetes.io/previousNamespaces": "default",
},
},
})
@@ -356,9 +356,9 @@ func TestGetMatchingResourcesByAnyId(t *testing.T) {
"metadata": map[string]interface{}{
"name": "new-bob",
"annotations": map[string]interface{}{
"config.kubernetes.io/previousKinds": "ConfigMap,ConfigMap",
"config.kubernetes.io/previousNames": "bob,bob2",
"config.kubernetes.io/previousNamespaces": "default,default",
"internal.config.kubernetes.io/previousKinds": "ConfigMap,ConfigMap",
"internal.config.kubernetes.io/previousNames": "bob,bob2",
"internal.config.kubernetes.io/previousNamespaces": "default,default",
},
},
})
@@ -370,9 +370,9 @@ func TestGetMatchingResourcesByAnyId(t *testing.T) {
"name": "new-bob",
"namespace": "new-happy",
"annotations": map[string]interface{}{
"config.kubernetes.io/previousKinds": "ConfigMap",
"config.kubernetes.io/previousNames": "bob",
"config.kubernetes.io/previousNamespaces": "happy",
"internal.config.kubernetes.io/previousKinds": "ConfigMap",
"internal.config.kubernetes.io/previousNames": "bob",
"internal.config.kubernetes.io/previousNamespaces": "happy",
},
},
})
@@ -384,9 +384,9 @@ func TestGetMatchingResourcesByAnyId(t *testing.T) {
"name": "charlie",
"namespace": "happy",
"annotations": map[string]interface{}{
"config.kubernetes.io/previousKinds": "ConfigMap",
"config.kubernetes.io/previousNames": "charlie",
"config.kubernetes.io/previousNamespaces": "default",
"internal.config.kubernetes.io/previousKinds": "ConfigMap",
"internal.config.kubernetes.io/previousNames": "charlie",
"internal.config.kubernetes.io/previousNamespaces": "default",
},
},
})
@@ -845,6 +845,8 @@ func TestAbsorbAll(t *testing.T) {
}))
w := makeMap1()
assert.NoError(t, w.AbsorbAll(makeMap2(types.BehaviorMerge)))
expected.RemoveBuildAnnotations()
w.RemoveBuildAnnotations()
assert.NoError(t, expected.ErrorIfNotEqualLists(w))
w = makeMap1()
assert.NoError(t, w.AbsorbAll(nil))
@@ -853,6 +855,7 @@ func TestAbsorbAll(t *testing.T) {
w = makeMap1()
w2 := makeMap2(types.BehaviorReplace)
assert.NoError(t, w.AbsorbAll(w2))
w2.RemoveBuildAnnotations()
assert.NoError(t, w2.ErrorIfNotEqualLists(w))
w = makeMap1()
w2 = makeMap2(types.BehaviorUnspecified)

View File

@@ -75,7 +75,9 @@ func (rf *Factory) makeOne(rn *yaml.RNode, o *types.GenArgs) *Resource {
if o == nil {
o = types.NewGenArgs(nil)
}
return &Resource{RNode: *rn, options: o}
resource := &Resource{RNode: *rn}
resource.SetOptions(o)
return resource
}
// SliceFromPatches returns a slice of resources given a patch path

View File

@@ -22,8 +22,6 @@ import (
// paired with metadata used by kustomize.
type Resource struct {
kyaml.RNode
options *types.GenArgs
refBy []resid.ResId
refVarNames []string
}
@@ -35,6 +33,8 @@ var BuildAnnotations = []string{
utils.BuildAnnotationPreviousNamespaces,
utils.BuildAnnotationAllowNameChange,
utils.BuildAnnotationAllowKindChange,
utils.BuildAnnotationsRefBy,
utils.BuildAnnotationsGenOptions,
}
func (r *Resource) ResetRNode(incoming *Resource) {
@@ -80,6 +80,8 @@ func (r *Resource) DeepCopy() *Resource {
// CopyMergeMetaDataFieldsFrom copies everything but the non-metadata in
// the resource.
// TODO: move to RNode, use GetMeta to improve performance.
// TODO: make a version of mergeStringMaps that is build-annotation aware
// to avoid repeatedly setting refby and genargs annotations
// Must remove the kustomize bit at the end.
func (r *Resource) CopyMergeMetaDataFieldsFrom(other *Resource) error {
if err := r.SetLabels(
@@ -87,7 +89,7 @@ func (r *Resource) CopyMergeMetaDataFieldsFrom(other *Resource) error {
return fmt.Errorf("copyMerge cannot set labels - %w", err)
}
if err := r.SetAnnotations(
mergeStringMaps(other.GetAnnotations(), r.GetAnnotations())); err != nil {
mergeStringMapsWithBuildAnnotations(other.GetAnnotations(), r.GetAnnotations())); err != nil {
return fmt.Errorf("copyMerge cannot set annotations - %w", err)
}
if err := r.SetName(other.GetName()); err != nil {
@@ -101,8 +103,6 @@ func (r *Resource) CopyMergeMetaDataFieldsFrom(other *Resource) error {
}
func (r *Resource) copyKustomizeSpecificFields(other *Resource) {
r.options = other.options
r.refBy = other.copyRefBy()
r.refVarNames = copyStringSlice(other.refVarNames)
}
@@ -144,10 +144,10 @@ func (r *Resource) ErrIfNotEquals(o *Resource) error {
func (r *Resource) ReferencesEqual(other *Resource) bool {
setSelf := make(map[resid.ResId]bool)
setOther := make(map[resid.ResId]bool)
for _, ref := range other.refBy {
for _, ref := range other.GetRefBy() {
setOther[ref] = true
}
for _, ref := range r.refBy {
for _, ref := range r.GetRefBy() {
if _, ok := setOther[ref]; !ok {
return false
}
@@ -156,15 +156,6 @@ func (r *Resource) ReferencesEqual(other *Resource) bool {
return len(setSelf) == len(setOther)
}
func (r *Resource) copyRefBy() []resid.ResId {
if r.refBy == nil {
return nil
}
s := make([]resid.ResId, len(r.refBy))
copy(s, r.refBy)
return s
}
func copyStringSlice(s []string) []string {
if s == nil {
return nil
@@ -284,7 +275,7 @@ func (r *Resource) String() string {
if err != nil {
return "<" + err.Error() + ">"
}
return strings.TrimSpace(string(bs)) + r.options.String()
return strings.TrimSpace(string(bs))
}
// AsYAML returns the resource in Yaml form.
@@ -306,20 +297,43 @@ func (r *Resource) MustYaml() string {
return string(yml)
}
func (r *Resource) getGenArgs() *types.GenArgs {
annotations := r.GetAnnotations()
if genOptsAnno, ok := annotations[utils.BuildAnnotationsGenOptions]; ok {
var genOpts types.GeneratorArgs
yaml.Unmarshal([]byte(genOptsAnno), &genOpts)
return types.NewGenArgs(&genOpts)
}
return nil
}
// SetOptions updates the generator options for the resource.
func (r *Resource) SetOptions(o *types.GenArgs) {
r.options = o
annotations := r.GetAnnotations()
if o.IsNilOrEmpty() {
if len(annotations) == 0 {
return
}
if o == nil {
delete(annotations, utils.BuildAnnotationsGenOptions)
}
} else {
b, _ := o.AsYaml()
annotations[utils.BuildAnnotationsGenOptions] = string(b)
}
r.SetAnnotations(annotations)
}
// Behavior returns the behavior for the resource.
func (r *Resource) Behavior() types.GenerationBehavior {
return r.options.Behavior()
return r.getGenArgs().Behavior()
}
// NeedHashSuffix returns true if a resource content
// hash should be appended to the name of the resource.
func (r *Resource) NeedHashSuffix() bool {
return r.options != nil && r.options.ShouldAddHashSuffixToName()
options := r.getGenArgs()
return options != nil && options.ShouldAddHashSuffixToName()
}
// OrgId returns the original, immutable ResId for the resource.
@@ -363,12 +377,18 @@ func (r *Resource) CurId() resid.ResId {
// GetRefBy returns the ResIds that referred to current resource
func (r *Resource) GetRefBy() []resid.ResId {
return r.refBy
var resIds []resid.ResId
asStrings := r.getCsvAnnotation(utils.BuildAnnotationsRefBy)
for _, s := range asStrings {
resIds = append(resIds, resid.FromString(s))
}
return resIds
}
// AppendRefBy appends a ResId into the refBy list
func (r *Resource) AppendRefBy(id resid.ResId) {
r.refBy = append(r.refBy, id)
// Using any type except fmt.Stringer here results in a compilation error
func (r *Resource) AppendRefBy(id fmt.Stringer) {
r.appendCsvAnnotation(utils.BuildAnnotationsRefBy, id.String())
}
// GetRefVarNames returns vars that refer to current resource
@@ -424,3 +444,17 @@ func mergeStringMaps(maps ...map[string]string) map[string]string {
}
return result
}
func mergeStringMapsWithBuildAnnotations(maps ...map[string]string) map[string]string {
result := mergeStringMaps(maps...)
for i := range BuildAnnotations {
if len(maps) > 0 {
if v, ok := maps[0][BuildAnnotations[i]]; ok {
result[BuildAnnotations[i]] = v
continue
}
}
delete(result, BuildAnnotations[i])
}
return result
}

View File

@@ -28,10 +28,10 @@ var testConfigMap = factory.FromMap(
},
})
const genArgOptions = "{nsfx:false,beh:unspecified}"
//nolint:gosec
const configMapAsString = `{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie","namespace":"hundred-acre-wood"}}`
const configMapAsStringWithOptions = `{"apiVersion":"v1","kind":"ConfigMap","metadata":{"annotations":` +
`{"internal.config.kubernetes.io/generatorOptions":"{}\n"},"name":"winnie","namespace":"hundred-acre-wood"}}`
var testDeployment = factory.FromMap(
map[string]interface{}{
@@ -43,6 +43,8 @@ var testDeployment = factory.FromMap(
})
const deploymentAsString = `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"pooh"}}`
const deploymentAsStringWithOptions = `{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":` +
`{"internal.config.kubernetes.io/generatorOptions":"{}\n"},"name":"pooh"}}`
func TestAsYAML(t *testing.T) {
expected := `apiVersion: apps/v1
@@ -66,17 +68,37 @@ func TestResourceString(t *testing.T) {
}{
{
in: testConfigMap,
s: configMapAsString + genArgOptions,
s: configMapAsString,
},
{
in: testDeployment,
s: deploymentAsString + genArgOptions,
s: deploymentAsString,
},
}
for _, test := range tests {
if test.in.String() != test.s {
t.Fatalf("Expected %s == %s", test.in.String(), test.s)
}
assert.Equal(t, test.in.String(), test.s)
}
}
func TestResourceStringWithOptionsAnnotations(t *testing.T) {
tests := []struct {
in *Resource
s string
}{
{
in: testConfigMap,
s: configMapAsStringWithOptions,
},
{
in: testDeployment,
s: deploymentAsStringWithOptions,
},
}
for _, test := range tests {
args := &types.GeneratorArgs{}
options := types.NewGenArgs(args)
test.in.SetOptions(options)
assert.Equal(t, test.in.String(), test.s)
}
}
@@ -717,9 +739,9 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret
config.kubernetes.io/previousNames: oldName
config.kubernetes.io/previousNamespaces: default
internal.config.kubernetes.io/previousKinds: Secret
internal.config.kubernetes.io/previousNames: oldName
internal.config.kubernetes.io/previousNamespaces: default
name: newName
`,
},
@@ -729,9 +751,9 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret
config.kubernetes.io/previousNames: oldName
config.kubernetes.io/previousNamespaces: default
internal.config.kubernetes.io/previousKinds: Secret
internal.config.kubernetes.io/previousNames: oldName
internal.config.kubernetes.io/previousNamespaces: default
name: oldName2
`,
newName: "newName",
@@ -740,9 +762,9 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret,Secret
config.kubernetes.io/previousNames: oldName,oldName2
config.kubernetes.io/previousNamespaces: default,default
internal.config.kubernetes.io/previousKinds: Secret,Secret
internal.config.kubernetes.io/previousNames: oldName,oldName2
internal.config.kubernetes.io/previousNamespaces: default,default
name: newName
`,
},
@@ -752,9 +774,9 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret
config.kubernetes.io/previousNames: oldName
config.kubernetes.io/previousNamespaces: default
internal.config.kubernetes.io/previousKinds: Secret
internal.config.kubernetes.io/previousNames: oldName
internal.config.kubernetes.io/previousNamespaces: default
name: oldName2
namespace: oldNamespace
`,
@@ -764,9 +786,9 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret,Secret
config.kubernetes.io/previousNames: oldName,oldName2
config.kubernetes.io/previousNamespaces: default,oldNamespace
internal.config.kubernetes.io/previousKinds: Secret,Secret
internal.config.kubernetes.io/previousNames: oldName,oldName2
internal.config.kubernetes.io/previousNamespaces: default,oldNamespace
name: newName
namespace: newNamespace
`,
@@ -814,9 +836,9 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret
config.kubernetes.io/previousNames: oldName
config.kubernetes.io/previousNamespaces: default
internal.config.kubernetes.io/previousKinds: Secret
internal.config.kubernetes.io/previousNames: oldName
internal.config.kubernetes.io/previousNamespaces: default
name: newName
`,
expected: []resid.ResId{
@@ -833,9 +855,9 @@ metadata:
kind: Secret
metadata:
annotations:
config.kubernetes.io/previousKinds: Secret,Secret
config.kubernetes.io/previousNames: oldName,oldName2
config.kubernetes.io/previousNamespaces: default,oldNamespace
internal.config.kubernetes.io/previousKinds: Secret,Secret
internal.config.kubernetes.io/previousNames: oldName,oldName2
internal.config.kubernetes.io/previousNamespaces: default,oldNamespace
name: newName
namespace: newNamespace
`,
@@ -1133,3 +1155,73 @@ spec:
t.Fatalf("expected '%s', got '%s'", expected, actual)
}
}
func TestRefBy(t *testing.T) {
r, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: Deployment
metadata:
name: clown
spec:
numReplicas: 1
`))
assert.NoError(t, err)
r.AppendRefBy(resid.FromString("gr1_ver1_knd1|ns1|name1"))
assert.Equal(t, r.RNode.MustString(), `apiVersion: v1
kind: Deployment
metadata:
name: clown
annotations:
internal.config.kubernetes.io/refBy: gr1_ver1_knd1|ns1|name1
spec:
numReplicas: 1
`)
assert.Equal(t, r.GetRefBy(), []resid.ResId{resid.FromString("gr1_ver1_knd1|ns1|name1")})
r.AppendRefBy(resid.FromString("gr2_ver2_knd2|ns2|name2"))
assert.Equal(t, r.RNode.MustString(), `apiVersion: v1
kind: Deployment
metadata:
name: clown
annotations:
internal.config.kubernetes.io/refBy: gr1_ver1_knd1|ns1|name1,gr2_ver2_knd2|ns2|name2
spec:
numReplicas: 1
`)
assert.Equal(t, r.GetRefBy(), []resid.ResId{
resid.FromString("gr1_ver1_knd1|ns1|name1"),
resid.FromString("gr2_ver2_knd2|ns2|name2"),
})
}
func TestOptions(t *testing.T) {
r, err := factory.FromBytes([]byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: example-configmap-test
`))
assert.NoError(t, err)
args := &types.GeneratorArgs{
Behavior: "merge",
Options: &types.GeneratorOptions{
DisableNameSuffixHash: true,
},
}
options := types.NewGenArgs(args)
r.SetOptions(options)
assert.Equal(t, r.RNode.MustString(), `apiVersion: v1
kind: ConfigMap
metadata:
name: example-configmap-test
annotations:
internal.config.kubernetes.io/generatorOptions: |
behavior: merge
options:
disableNameSuffixHash: true
`)
assert.Equal(t, r.Behavior(), types.BehaviorMerge)
assert.Equal(t, r.NeedHashSuffix(), !args.Options.DisableNameSuffixHash)
}

View File

@@ -148,6 +148,13 @@ func (th *HarnessEnhanced) ResetLoaderRoot(root string) {
}
func (th *HarnessEnhanced) LoadAndRunGenerator(
config string) resmap.ResMap {
rm := th.LoadAndRunGeneratorWithBuildAnnotations(config)
rm.RemoveBuildAnnotations()
return rm
}
func (th *HarnessEnhanced) LoadAndRunGeneratorWithBuildAnnotations(
config string) resmap.ResMap {
res, err := th.rf.RF().FromBytes([]byte(config))
if err != nil {
@@ -162,7 +169,6 @@ func (th *HarnessEnhanced) LoadAndRunGenerator(
if err != nil {
th.t.Fatalf("generate err: %v", err)
}
rm.RemoveBuildAnnotations()
return rm
}

View File

@@ -6,6 +6,8 @@ package types
import (
"strconv"
"strings"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// GenArgs is a facade over GeneratorArgs, exposing a few readonly properties.
@@ -44,3 +46,16 @@ func (g *GenArgs) Behavior() GenerationBehavior {
}
return NewGenerationBehavior(g.args.Behavior)
}
// IsNilOrEmpty returns true if g is nil or if the args are empty
func (g *GenArgs) IsNilOrEmpty() bool {
return g == nil || g.args == nil
}
// AsYaml returns a yaml marshalling of the underlying Genargs
func (g *GenArgs) AsYaml() ([]byte, error) {
if g == nil {
return yaml.Marshal(nil)
}
return yaml.Marshal(g.args)
}

View File

@@ -327,49 +327,51 @@ A function SHOULD preserve comments when input serialization format is YAML.
This allows for human authoring of configuration to coexist with changes made by
functions.
### Annotations
### Internal Annotations
The orchestrator annotates resources in the wire format with annotation prefix
`config.kubernetes.io`. These annotations are not persisted when the
orchestrator writes the resources to the filesystem. The orchestrator sets this
annotation when reading files from the local filesystem and removes the
annotation when writing the output of functions back to the filesystem.
For orchestration purposes, the orchestrator will use a set of annotations,
referred to as _internal annotations_, on resources in `Resources.items`. These
annotations are not persisted to resource manifests on the filesystem: The
orchestrator sets this annotation when reading files from the local filesystem
and removes the annotation when writing the output of functions back to the
filesystem.
In general, a function MUST NOT modify these annotations except the ones
explicitly listed below.
Annotation prefix `internal.config.kubernetes.io` is reserved for use for
internal annotations. In general, a function MUST NOT modify these annotations with
the exception of the specific annotations listed below. This enables orchestrators to add additional internal annotations, without requiring changes to existing functions.
#### `config.kubernetes.io/path`
#### `internal.config.kubernetes.io/path`
Records the slash-delimited, OS-agnostic, relative file path to a resource. The
path is relative to a fix location on the filesystem. Different orchestrator
implementations can choose different fixed points.
A function SHOULD NOT modify this annotation.
A function SHOULD NOT modify these annotations.
Example:
```yaml
metadata:
annotations:
config.kubernetes.io/path: "relative/file/path.yaml"
internal.config.kubernetes.io/path: "relative/file/path.yaml"
```
#### `config.kubernetes.io/index`
#### `internal.config.kubernetes.io/index`
Records the index of a Resource in file. In a multi-object YAML file, resources
are separated by three dashes (`---`), and the index represents the position of
the Resource starting from zero. When this annotation is not specified, it
implies a value of `0`.
A function SHOULD NOT modify this annotation.
A function SHOULD NOT modify these annotations.
Example:
```yaml
metadata:
annotations:
config.kubernetes.io/path: "relative/file/path.yaml"
config.kubernetes.io/index: 2
internal.config.kubernetes.io/path: "relative/file/path.yaml"
internal.config.kubernetes.io/index: 2
```
This represents the third resource in the file.

View File

@@ -0,0 +1,11 @@
# Manifest Annotations
This document lists the annotations that can be declared in resource manifests.
### `config.kubernetes.io/local-config`
A value of `"true"` for this annotation declares that the resource is only consumed by
client-side tooling and should not be applied to the API server.
A value of `"false"` can be used to declare that a resource should be applied to
the API server even when it is assumed to be local.

View File

@@ -16,7 +16,7 @@ require (
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/inf.v0 v0.9.1
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e
sigs.k8s.io/kustomize/kyaml v0.10.20
sigs.k8s.io/kustomize/kyaml v0.11.0
)
replace sigs.k8s.io/kustomize/kyaml => ../../kyaml

View File

@@ -246,4 +246,5 @@ k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -21,12 +21,13 @@ import (
func NewAnnotateRunner(parent string) *AnnotateRunner {
r := &AnnotateRunner{}
c := &cobra.Command{
Use: "annotate [DIR]",
Args: cobra.MaximumNArgs(1),
Short: commands.AnnotateShort,
Long: commands.AnnotateLong,
Example: commands.AnnotateExamples,
RunE: r.runE,
Use: "annotate [DIR]",
Args: cobra.MaximumNArgs(1),
Short: commands.AnnotateShort,
Long: commands.AnnotateLong,
Example: commands.AnnotateExamples,
RunE: r.runE,
Deprecated: "use the `commonAnnotations` field in your kustomization file.",
}
runner.FixDocs(parent, c)
r.Command = c

View File

@@ -559,7 +559,7 @@ added annotations in the package
expected := strings.Replace(test.expected, "${baseDir}", baseDir, -1)
expectedNormalized := strings.Replace(expected, "\\", "/", -1)
if !assert.Equal(t, expectedNormalized, actualNormalized) {
if !assert.Contains(t, actualNormalized, expectedNormalized) {
t.FailNow()
}
})

View File

@@ -32,6 +32,8 @@ func NewCreateSetterRunner(parent string) *CreateSetterRunner {
Example: commands.CreateSetterExamples,
PreRunE: r.preRunE,
RunE: r.runE,
Deprecated: "setter commands will no longer be available in kustomize v5.\n" +
"See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.",
}
set.Flags().StringVar(&r.FieldValue, "value", "",
"optional flag, alternative to specifying the value as an argument. e.g. used to specify values that start with '-'")

View File

@@ -869,7 +869,7 @@ setter with name "namespace" already exists, if you want to modify it, please de
expected := strings.Replace(test.expected, "${baseDir}", baseDir, -1)
expectedNormalized := strings.Replace(expected, "\\", "/", -1)
if !assert.Equal(t, expectedNormalized, actualNormalized) {
if !assert.Contains(t, actualNormalized, expectedNormalized) {
t.FailNow()
}
})

View File

@@ -23,6 +23,8 @@ func NewCreateSubstitutionRunner(parent string) *CreateSubstitutionRunner {
Args: cobra.ExactArgs(2),
PreRun: r.preRun,
RunE: r.runE,
Deprecated: "imperative substitutions will no longer be available in kustomize v5.\n" +
"See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.",
}
cs.Flags().StringVar(&r.CreateSubstitution.FieldName, "field", "",
"name of the field to set -- e.g. --field image")

View File

@@ -506,7 +506,7 @@ created substitution "image-tag"`,
expected := strings.Replace(test.expected, "${baseDir}", baseDir, -1)
expectedNormalized := strings.Replace(expected, "\\", "/", -1)
if !assert.Equal(t, strings.TrimSpace(expectedNormalized), strings.TrimSpace(actualNormalized)) {
if !assert.Contains(t, strings.TrimSpace(actualNormalized), strings.TrimSpace(expectedNormalized)) {
t.FailNow()
}
})

View File

@@ -26,6 +26,8 @@ func GetInitRunner(name string) *InitRunner {
Long: commands.InitLong,
Example: commands.InitExamples,
RunE: r.runE,
Deprecated: "setter commands and substitutions will no longer be available in kustomize v5.\n" +
"See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.",
}
runner.FixDocs(name, c)
r.Command = c

View File

@@ -31,6 +31,8 @@ func NewListSettersRunner(parent string) *ListSettersRunner {
Example: commands.ListSettersExamples,
PreRunE: r.preRunE,
RunE: r.runE,
Deprecated: "setter commands will no longer be available in kustomize v5.\n" +
"See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.",
}
c.Flags().BoolVar(&r.Markdown, "markdown", false,
"output as github markdown")

View File

@@ -525,7 +525,7 @@ test/testdata/dataset-with-setters/mysql/
// normalize path format for windows
actualNormalized := strings.Replace(actual.String(), "\\", "/", -1)
if !assert.Equal(t, strings.TrimSpace(test.expected), strings.TrimSpace(actualNormalized)) {
if !assert.Contains(t, strings.TrimSpace(actualNormalized), strings.TrimSpace(test.expected)) {
t.FailNow()
}
})

View File

@@ -27,6 +27,8 @@ func NewSetRunner(parent string) *SetRunner {
Example: commands.SetExamples,
PreRunE: r.preRunE,
RunE: r.runE,
Deprecated: "setter commands will no longer be available in kustomize v5.\n" +
"See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.",
}
runner.FixDocs(parent, c)
r.Command = c

View File

@@ -1137,7 +1137,7 @@ set 1 field(s) of setter "namespace" to value "otherspace"
expectedNormalized := strings.Replace(
strings.Replace(expected, "\\", "/", -1),
"//", "/", -1)
if !assert.Equal(t, strings.TrimSpace(expectedNormalized), strings.TrimSpace(actualNormalized)) {
if !assert.Contains(t, strings.TrimSpace(actualNormalized), strings.TrimSpace(expectedNormalized)) {
t.FailNow()
}
})

View File

@@ -542,13 +542,13 @@ kind: Input
metadata:
name: foo
annotations:
a-bool-value: true
a-int-value: 2
a-string-value: a
config.kubernetes.io/function: |
starlark:
path: script.star
name: fn
a-bool-value: true
a-int-value: 2
a-string-value: a
data:
boolValue: true
intValue: 2

View File

@@ -25,6 +25,8 @@ func GetFmtRunner(name string) *FmtRunner {
Example: commands.FmtExamples,
RunE: r.runE,
PreRunE: r.preRunE,
Deprecated: "imperative formatting will no longer be available in kustomize v5.\n" +
"Declare a formatting transformer in your kustomization instead.",
}
runner.FixDocs(name, c)
c.Flags().StringVar(&r.FilenamePattern, "pattern", filters.DefaultFilenamePattern,

View File

@@ -78,7 +78,7 @@ func TestFmtCommand_stdin(t *testing.T) {
assert.NoError(t, err)
// verify the output
assert.Equal(t, string(testyaml.FormattedYaml1), out.String())
assert.Contains(t, out.String(), string(testyaml.FormattedYaml1))
}
// TestCmd_filesAndstdin verifies that if both files and stdin input are provided, only
@@ -238,7 +238,7 @@ formatted resource files in the package
expected := strings.Replace(test.expected, "${baseDir}", baseDir, -1)
expectedNormalized := strings.Replace(expected, "\\", "/", -1)
if !assert.Equal(t, strings.TrimSpace(expectedNormalized), strings.TrimSpace(actualNormalized)) {
if !assert.Contains(t, strings.TrimSpace(actualNormalized), strings.TrimSpace(expectedNormalized)) {
t.FailNow()
}
})

View File

@@ -43,7 +43,9 @@ kind: Krmfile
t.FailNow()
}
if !assert.Equal(t, "", b.String()) {
if !assert.Equal(t, `Command "init" is deprecated, setter commands and substitutions will no longer be available in kustomize v5.
See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.
`, b.String()) {
t.FailNow()
}
}
@@ -78,7 +80,9 @@ kind: Krmfile
t.FailNow()
}
if !assert.Equal(t, "", b.String()) {
if !assert.Equal(t, `Command "init" is deprecated, setter commands and substitutions will no longer be available in kustomize v5.
See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.
`, b.String()) {
t.FailNow()
}
}

View File

@@ -19,6 +19,8 @@ func GetMergeRunner(name string) *MergeRunner {
Long: commands.MergeLong,
Example: commands.MergeExamples,
RunE: r.runE,
Deprecated: "this will no longer be available in kustomize v5.\n" +
"See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.",
}
runner.FixDocs(name, c)
r.Command = c

View File

@@ -18,6 +18,8 @@ func GetMerge3Runner(name string) *Merge3Runner {
Long: commands.Merge3Long,
Example: commands.Merge3Examples,
RunE: r.runE,
Deprecated: "this will no longer be available in kustomize v5.\n" +
"See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.",
}
runner.FixDocs(name, c)
c.Flags().StringVar(&r.ancestor, "ancestor", "",

View File

@@ -6,8 +6,8 @@ require (
github.com/rakyll/statik v0.1.7
github.com/spf13/cobra v1.0.0
github.com/stretchr/testify v1.5.1
sigs.k8s.io/kustomize/api v0.8.10
sigs.k8s.io/kustomize/kyaml v0.10.20
sigs.k8s.io/kustomize/api v0.8.11
sigs.k8s.io/kustomize/kyaml v0.11.0
)
replace sigs.k8s.io/kustomize/api => ../../api

View File

@@ -32,7 +32,7 @@ chart repository.
This example defines the `helm` command as
<!-- @defineHelmCommand @testHelm -->
```
helmCommand=~/go/bin/helmV3
helmCommand=${MYGOBIN:-~/go/bin}/helmV3
```
This value is needed for testing this example in CI/CD.

View File

@@ -19,6 +19,10 @@
set -e
# Unset CDPATH to restore default cd behavior. An exported CDPATH can
# cause cd to output the current directory to STDOUT.
unset CDPATH
where=$PWD
release_url=https://api.github.com/repos/kubernetes-sigs/kustomize/releases

View File

@@ -29,6 +29,7 @@ fi
# We test against the latest release, and HEAD, and presumably
# any branch using this label, so it should probably get
# a new value.
export MYGOBIN
mdrip --mode test --blockTimeOut 15m \
--label testAgainstLatestRelease examples

View File

@@ -8,26 +8,38 @@ import (
"os/exec"
"github.com/spf13/cobra"
"sigs.k8s.io/yaml"
)
var format string
// NewCmdFetch makes a new fetch command.
func NewCmdFetch(w io.Writer) *cobra.Command {
infoCmd := cobra.Command{
fetchCmd := cobra.Command{
Use: "fetch",
Short: `Fetches the OpenAPI specification from the current kubernetes cluster specified
in the user's kubeconfig`,
Example: `kustomize openapi fetch`,
Run: func(cmd *cobra.Command, args []string) {
printSchema(w)
RunE: func(cmd *cobra.Command, args []string) error {
return printSchema(w)
},
}
return &infoCmd
fetchCmd.Flags().StringVar(
&format,
"format",
"json",
"Specify format for fetched schema ('json' or 'yaml')")
return &fetchCmd
}
func printSchema(w io.Writer) {
func printSchema(w io.Writer) error {
if format != "json" && format != "yaml" {
return fmt.Errorf("format must be either 'json' or 'yaml'")
}
errMsg := `
Error fetching schema from cluster.
Please make sure kubectl is installed and its context is set correctly.
Please make sure kubectl is installed, its context is set correctly, and your cluster is up.
Installation and setup instructions: https://kubernetes.io/docs/tasks/tools/install-kubectl/`
command := exec.Command("kubectl", []string{"get", "--raw", "/openapi/v2"}...)
@@ -36,9 +48,10 @@ Installation and setup instructions: https://kubernetes.io/docs/tasks/tools/inst
command.Stdout = &stdout
command.Stderr = &stderr
err := command.Run()
if err != nil || stdout.String() == "" {
fmt.Fprintln(w, err, stderr.String()+errMsg)
return
if err != nil {
return fmt.Errorf("%w\n%s", err, stderr.String()+errMsg)
} else if stdout.String() == "" {
return fmt.Errorf(stderr.String() + errMsg)
}
// format and output
@@ -46,5 +59,14 @@ Installation and setup instructions: https://kubernetes.io/docs/tasks/tools/inst
output := stdout.Bytes()
json.Unmarshal(output, &jsonSchema)
output, _ = json.MarshalIndent(jsonSchema, "", " ")
if format == "yaml" {
output, err = yaml.JSONToYAML(output)
if err != nil {
return err
}
}
fmt.Fprintln(w, string(output))
return nil
}

View File

@@ -8,9 +8,9 @@ require (
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
sigs.k8s.io/kustomize/api v0.8.10
sigs.k8s.io/kustomize/cmd/config v0.9.12
sigs.k8s.io/kustomize/kyaml v0.10.20
sigs.k8s.io/kustomize/api v0.8.11
sigs.k8s.io/kustomize/cmd/config v0.9.13
sigs.k8s.io/kustomize/kyaml v0.11.0
sigs.k8s.io/yaml v1.2.0
)

View File

@@ -74,6 +74,8 @@ type CommandResultsChecker struct {
// Command provides the function to run.
Command func() *cobra.Command
*checkerCore
}
// Assert runs the command with the input provided in each valid test directory
@@ -85,39 +87,51 @@ func (rc *CommandResultsChecker) Assert(t *testing.T) bool {
if rc.InputFilenameGlob == "" {
rc.InputFilenameGlob = DefaultInputFilenameGlob
}
checker := newResultsChecker(
rc.TestDataDirectory, rc.ExpectedOutputFilename, rc.ExpectedErrorFilename,
rc.OutputAssertionFunc, rc.ErrorAssertionFunc,
rc.UpdateExpectedFromActual,
)
checker.assert(t, func() (string, string) {
_, err := os.Stat(rc.ConfigInputFilename)
if os.IsNotExist(err) {
t.Errorf("Test case is missing FunctionConfig input file (default: %s)", DefaultConfigInputFilename)
}
require.NoError(t, err)
args := []string{rc.ConfigInputFilename}
if rc.InputFilenameGlob != "" {
inputs, err := filepath.Glob(rc.InputFilenameGlob)
require.NoError(t, err)
args = append(args, inputs...)
}
var stdOut, stdErr bytes.Buffer
cmd := rc.Command()
cmd.SetArgs(args)
cmd.SetOut(&stdOut)
cmd.SetErr(&stdErr)
err = cmd.Execute()
return stdOut.String(), stdErr.String()
})
rc.checkerCore = &checkerCore{
testDataDirectory: rc.TestDataDirectory,
expectedOutputFilename: rc.ExpectedOutputFilename,
expectedErrorFilename: rc.ExpectedErrorFilename,
updateExpectedFromActual: rc.UpdateExpectedFromActual,
outputAssertionFunc: rc.OutputAssertionFunc,
errorAssertionFunc: rc.ErrorAssertionFunc,
}
rc.checkerCore.setDefaults()
runAllTestCases(t, rc)
return true
}
func (rc *CommandResultsChecker) isTestDir(path string) bool {
return atLeastOneFileExists(
filepath.Join(path, rc.ConfigInputFilename),
filepath.Join(path, rc.checkerCore.expectedOutputFilename),
filepath.Join(path, rc.checkerCore.expectedErrorFilename),
)
}
func (rc *CommandResultsChecker) runInCurrentDir(t *testing.T) (string, string) {
_, err := os.Stat(rc.ConfigInputFilename)
if os.IsNotExist(err) {
t.Errorf("Test case is missing FunctionConfig input file (default: %s)", DefaultConfigInputFilename)
}
require.NoError(t, err)
args := []string{rc.ConfigInputFilename}
if rc.InputFilenameGlob != "" {
inputs, err := filepath.Glob(rc.InputFilenameGlob)
require.NoError(t, err)
args = append(args, inputs...)
}
var stdOut, stdErr bytes.Buffer
cmd := rc.Command()
cmd.SetArgs(args)
cmd.SetOut(&stdOut)
cmd.SetErr(&stdErr)
_ = cmd.Execute()
return stdOut.String(), stdErr.String()
}
// ProcessorResultsChecker tests a processor function by running it with predefined inputs
// and comparing the outputs to expected results.
type ProcessorResultsChecker struct {
@@ -159,6 +173,8 @@ type ProcessorResultsChecker struct {
// Processor returns a ResourceListProcessor to run.
Processor func() framework.ResourceListProcessor
*checkerCore
}
// Assert runs the processor with the input provided in each valid test directory
@@ -167,37 +183,50 @@ func (rc *ProcessorResultsChecker) Assert(t *testing.T) bool {
if rc.InputFilename == "" {
rc.InputFilename = DefaultInputFilename
}
checker := newResultsChecker(
rc.TestDataDirectory, rc.ExpectedOutputFilename, rc.ExpectedErrorFilename,
rc.OutputAssertionFunc, rc.ErrorAssertionFunc,
rc.UpdateExpectedFromActual,
)
checker.assert(t, func() (string, string) {
_, err := os.Stat(rc.InputFilename)
if os.IsNotExist(err) {
t.Error("Test case is missing input file")
}
require.NoError(t, err)
actualOutput := bytes.NewBuffer([]byte{})
rlBytes, err := ioutil.ReadFile(rc.InputFilename)
require.NoError(t, err)
rw := kio.ByteReadWriter{
Reader: bytes.NewBuffer(rlBytes),
Writer: actualOutput,
}
err = framework.Execute(rc.Processor(), &rw)
if err != nil {
require.NotEmptyf(t, err.Error(), "processor returned error with empty message")
return actualOutput.String(), err.Error()
}
return actualOutput.String(), ""
})
rc.checkerCore = &checkerCore{
testDataDirectory: rc.TestDataDirectory,
expectedOutputFilename: rc.ExpectedOutputFilename,
expectedErrorFilename: rc.ExpectedErrorFilename,
updateExpectedFromActual: rc.UpdateExpectedFromActual,
outputAssertionFunc: rc.OutputAssertionFunc,
errorAssertionFunc: rc.ErrorAssertionFunc,
}
rc.checkerCore.setDefaults()
runAllTestCases(t, rc)
return true
}
func (rc *ProcessorResultsChecker) isTestDir(path string) bool {
return atLeastOneFileExists(
filepath.Join(path, rc.InputFilename),
filepath.Join(path, rc.checkerCore.expectedOutputFilename),
filepath.Join(path, rc.checkerCore.expectedErrorFilename),
)
}
func (rc *ProcessorResultsChecker) runInCurrentDir(t *testing.T) (string, string) {
_, err := os.Stat(rc.InputFilename)
if os.IsNotExist(err) {
t.Errorf("Test case is missing input file (default: %s)", DefaultInputFilename)
}
require.NoError(t, err)
actualOutput := bytes.NewBuffer([]byte{})
rlBytes, err := ioutil.ReadFile(rc.InputFilename)
require.NoError(t, err)
rw := kio.ByteReadWriter{
Reader: bytes.NewBuffer(rlBytes),
Writer: actualOutput,
}
err = framework.Execute(rc.Processor(), &rw)
if err != nil {
require.NotEmptyf(t, err.Error(), "processor returned error with empty message")
return actualOutput.String(), err.Error()
}
return actualOutput.String(), ""
}
type AssertionFunc func(t *testing.T, expected string, actual string)
// RequireEachLineMatches is an AssertionFunc that treats each line of expected string
@@ -223,8 +252,35 @@ func standardizeSpacing(s string) string {
return strings.ReplaceAll(strings.TrimSpace(s), "\r\n", "\n")
}
// resultsChecker implements the core logic shared by all results checking types.
type resultsChecker struct {
// resultsChecker is implemented by ProcessorResultsChecker and CommandResultsChecker, partially via checkerCore
type resultsChecker interface {
// TestCasesRun returns a list of the test case directories that have been seen.
TestCasesRun() (paths []string)
// rootDir is the root of the tree of test directories to be searched for fixtures.
rootDir() string
// isTestDir takes the name of directory and returns whether or not it contains the files required to be a test case.
isTestDir(dir string) bool
// runInCurrentDir executes the code the checker is checking in the context of the current working directory.
runInCurrentDir(t *testing.T) (stdOut, stdErr string)
// resetTestCasesRun resets the list of test case directories seen.
resetTestCasesRun()
// recordTestCase adds to the list of test case directories seen.
recordTestCase(path string)
// readAssertionFiles returns the contents of the output and error fixtures
readAssertionFiles(t *testing.T) (expectedOutput string, expectedError string)
// shouldUpdateFixtures indicates whether or not this checker is currently being used to update fixtures instead of run them.
shouldUpdateFixtures() bool
// updateFixtures modifies the test fixture files to match the given content
updateFixtures(t *testing.T, actualOutput string, actualError string)
// assertOutputMatches compares the expected output to the output recieved.
assertOutputMatches(t *testing.T, expected string, actual string)
// assertErrorMatches compares teh expected error to the error received.
assertErrorMatches(t *testing.T, expected string, actual string)
}
// checkerCore implements the resultsChecker methods that are shared between the two checker types
type checkerCore struct {
testDataDirectory string
expectedOutputFilename string
expectedErrorFilename string
@@ -232,20 +288,10 @@ type resultsChecker struct {
outputAssertionFunc AssertionFunc
errorAssertionFunc AssertionFunc
testsCasesRun int
testsCasesRun []string
}
func newResultsChecker(testDataDir string, outputFilename string, errorFilename string,
outputAsserter AssertionFunc, errorAsserter AssertionFunc,
updateFixtures bool) *resultsChecker {
rc := resultsChecker{
testDataDirectory: testDataDir,
expectedOutputFilename: outputFilename,
expectedErrorFilename: errorFilename,
updateExpectedFromActual: updateFixtures,
outputAssertionFunc: outputAsserter,
errorAssertionFunc: errorAsserter,
}
func (rc *checkerCore) setDefaults() {
if rc.testDataDirectory == "" {
rc.testDataDirectory = DefaultTestDataDirectory
}
@@ -261,73 +307,47 @@ func newResultsChecker(testDataDir string, outputFilename string, errorFilename
if rc.errorAssertionFunc == nil {
rc.errorAssertionFunc = RequireEachLineMatches
}
return &rc
}
// assert traverses TestDataDirectory to find test cases, calls getResult to invoke the function
// under test in each directory, and then runs assertions on the returned output and error results.
// It triggers a test failure if no valid test directories were found.
func (rc *resultsChecker) assert(t *testing.T, getResult func() (string, string)) {
err := filepath.Walk(rc.testDataDirectory,
func(path string, info os.FileInfo, err error) error {
require.NoError(t, err)
if !info.IsDir() {
// skip non-directories
return nil
}
rc.runDirectoryTestCase(t, path, getResult)
return nil
})
require.NoError(t, err)
require.NotZero(t, rc.testsCasesRun, "No complete test cases found in %s", rc.testDataDirectory)
func (rc *checkerCore) rootDir() string {
return rc.testDataDirectory
}
func (rc *resultsChecker) runDirectoryTestCase(t *testing.T, path string, getResult func() (string, string)) {
// cd into the directory so we can test functions that refer
// local files by relative paths
d, err := os.Getwd()
require.NoError(t, err)
func (rc *checkerCore) TestCasesRun() []string {
return rc.testsCasesRun
}
defer func() { require.NoError(t, os.Chdir(d)) }()
require.NoError(t, os.Chdir(path))
func (rc *checkerCore) resetTestCasesRun() {
rc.testsCasesRun = []string{}
}
expectedOutput, expectedError := rc.readAssertionFiles(t)
if expectedError == "" && expectedOutput == "" && !rc.updateExpectedFromActual {
// not a test directory: missing expectations and updateExpectedFromActual == false
return
func (rc *checkerCore) recordTestCase(s string) {
rc.testsCasesRun = append(rc.testsCasesRun, s)
}
func (rc *checkerCore) shouldUpdateFixtures() bool {
return rc.updateExpectedFromActual
}
func (rc *checkerCore) updateFixtures(t *testing.T, actualOutput string, actualError string) {
if actualError != "" {
require.NoError(t, ioutil.WriteFile(rc.expectedErrorFilename, []byte(actualError), 0600))
}
// run the test
t.Run(path, func(t *testing.T) {
rc.testsCasesRun += 1
actualOutput, actualError := getResult()
// Configured to update the assertion files instead of comparing them
if rc.updateExpectedFromActual {
if actualError != "" {
require.NoError(t, ioutil.WriteFile(rc.expectedErrorFilename, []byte(actualError), 0600))
}
if len(actualOutput) > 0 {
require.NoError(t, ioutil.WriteFile(rc.expectedOutputFilename, []byte(actualOutput), 0600))
}
t.Skip("Updated fixtures for test case")
}
// Compare the results to the assertion files
if expectedError != "" {
// We expected an error, so make sure there was one
require.NotEmptyf(t, actualError, "test expected an error but message was empty, and output was:\n%s", actualOutput)
rc.errorAssertionFunc(t, expectedError, actualError)
} else {
// We didn't expect an error, and the output should match
require.Emptyf(t, actualError, "test expected no error but got an error message, and output was:\n%s", actualOutput)
rc.outputAssertionFunc(t, expectedOutput, actualOutput)
}
})
if len(actualOutput) > 0 {
require.NoError(t, ioutil.WriteFile(rc.expectedOutputFilename, []byte(actualOutput), 0600))
}
t.Skip("Updated fixtures for test case")
}
// readAssertionFiles reads the expected results and error files
func (rc *resultsChecker) readAssertionFiles(t *testing.T) (string, string) {
func (rc *checkerCore) assertOutputMatches(t *testing.T, expected string, actual string) {
rc.outputAssertionFunc(t, expected, actual)
}
func (rc *checkerCore) assertErrorMatches(t *testing.T, expected string, actual string) {
rc.errorAssertionFunc(t, expected, actual)
}
func (rc *checkerCore) readAssertionFiles(t *testing.T) (string, string) {
// read the expected results
var expectedOutput, expectedError string
if rc.expectedOutputFilename != "" {
@@ -360,3 +380,65 @@ func (rc *resultsChecker) readAssertionFiles(t *testing.T) (string, string) {
}
return expectedOutput, expectedError
}
// runAllTestCases traverses rootDir to find test cases, calls getResult to invoke the function
// under test in each directory, and then runs assertions on the returned output and error results.
// It triggers a test failure if no valid test directories were found.
func runAllTestCases(t *testing.T, checker resultsChecker) {
checker.resetTestCasesRun()
err := filepath.Walk(checker.rootDir(),
func(path string, info os.FileInfo, err error) error {
require.NoError(t, err)
if info.IsDir() && checker.isTestDir(path) {
runDirectoryTestCase(t, path, checker)
}
return nil
})
require.NoError(t, err)
require.NotZero(t, len(checker.TestCasesRun()), "No complete test cases found in %s", checker.rootDir())
}
func runDirectoryTestCase(t *testing.T, path string, checker resultsChecker) {
// cd into the directory so we can test functions that refer to local files by relative paths
d, err := os.Getwd()
require.NoError(t, err)
defer func() { require.NoError(t, os.Chdir(d)) }()
require.NoError(t, os.Chdir(path))
expectedOutput, expectedError := checker.readAssertionFiles(t)
if expectedError == "" && expectedOutput == "" && !checker.shouldUpdateFixtures() {
t.Fatalf("test directory %s must include either expected output or expected error fixture", path)
}
// run the test
t.Run(path, func(t *testing.T) {
checker.recordTestCase(path)
actualOutput, actualError := checker.runInCurrentDir(t)
// Configured to update the assertion files instead of comparing them
if checker.shouldUpdateFixtures() {
checker.updateFixtures(t, actualOutput, actualError)
}
// Compare the results to the assertion files
if expectedError != "" {
// We expected an error, so make sure there was one
require.NotEmptyf(t, actualError, "test expected an error but message was empty, and output was:\n%s", actualOutput)
checker.assertErrorMatches(t, expectedError, actualError)
} else {
// We didn't expect an error, and the output should match
require.Emptyf(t, actualError, "test expected no error but got an error message, and output was:\n%s", actualOutput)
checker.assertOutputMatches(t, expectedOutput, actualOutput)
}
})
}
func atLeastOneFileExists(files ...string) bool {
for _, file := range files {
if _, err := os.Stat(file); err == nil {
return true
}
}
return false
}

View File

@@ -0,0 +1,68 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package frameworktestutil
import (
"path/filepath"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/fn/framework/command"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestProcessorResultsChecker_UpdateExpectedFromActual(t *testing.T) {
dir := filepath.FromSlash("testdata/update_expectations/processor")
checker := ProcessorResultsChecker{
TestDataDirectory: dir,
UpdateExpectedFromActual: true,
Processor: testProcessor,
}
// This should result in the test being skipped. If no tests are found, it will instead fail.
checker.Assert(t)
require.Contains(t, checker.TestCasesRun(), filepath.Join(dir, "important_subdir"))
checker.UpdateExpectedFromActual = false
// This time should inherently pass
checker.Assert(t)
require.Contains(t, checker.TestCasesRun(), filepath.Join(dir, "important_subdir"))
}
func TestCommandResultsChecker_UpdateExpectedFromActual(t *testing.T) {
dir := filepath.FromSlash("testdata/update_expectations/command")
checker := CommandResultsChecker{
TestDataDirectory: dir,
UpdateExpectedFromActual: true,
Command: testCommand,
}
// This should result in the test being skipped. If no tests are found, it will instead fail.
checker.Assert(t)
require.Contains(t, checker.TestCasesRun(), filepath.Join(dir, "important_subdir"))
checker.UpdateExpectedFromActual = false
// This time should inherently pass
checker.Assert(t)
require.Contains(t, checker.TestCasesRun(), filepath.Join(dir, "important_subdir"))
}
func testCommand() *cobra.Command {
return command.Build(testProcessor(), command.StandaloneEnabled, false)
}
func testProcessor() framework.ResourceListProcessor {
return framework.SimpleProcessor{
Filter: kio.FilterFunc(func(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
for _, node := range nodes {
err := node.SetAnnotations(map[string]string{"updated": "true"})
if err != nil {
return nil, err
}
}
return nodes, nil
}),
}
}

View File

@@ -0,0 +1,7 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
apiVersion: example.com/v1alpha1
kind: Demo
spec:
value: a

View File

@@ -0,0 +1,16 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-1
annotations:
updated: "true"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-2
annotations:
updated: "true"

View File

@@ -0,0 +1,16 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-1
annotations:
baz: foo
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-2
annotations:
foo: bar

View File

@@ -0,0 +1,20 @@
apiVersion:
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: test-1
annotations:
updated: "true"
- apiVersion: apps/v1
kind: Deployment
metadata:
name: test-2
annotations:
updated: "true"
functionConfig:
apiVersion: example.com/v1alpha1
kind: Demo
spec:
value: a

View File

@@ -0,0 +1,22 @@
# Copyright 2019 The Kubernetes Authors.
# SPDX-License-Identifier: Apache-2.0
kind: ResourceList
items:
- apiVersion: apps/v1
kind: Deployment
metadata:
name: test-1
annotations:
baz: foo
- apiVersion: apps/v1
kind: Deployment
metadata:
name: test-2
annotations:
foo: bar
functionConfig:
apiVersion: example.com/v1alpha1
kind: Demo
spec:
value: a

View File

@@ -15,6 +15,7 @@ import (
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/order"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
@@ -169,8 +170,8 @@ func (c *FunctionFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
return nil, err
}
// copy the comments from the inputs to the outputs
if err := c.setComments(output); err != nil {
// copy the comments and sync the order of fields from the inputs to the outputs
if err := c.copyCommentsAndSyncOrder(output); err != nil {
return nil, err
}
@@ -210,7 +211,7 @@ func (c *FunctionFilter) setIds(nodes []*yaml.RNode) error {
return nil
}
func (c *FunctionFilter) setComments(nodes []*yaml.RNode) error {
func (c *FunctionFilter) copyCommentsAndSyncOrder(nodes []*yaml.RNode) error {
for i := range nodes {
node := nodes[i]
anID, err := node.Pipe(yaml.GetAnnotation(idAnnotation))
@@ -229,6 +230,9 @@ func (c *FunctionFilter) setComments(nodes []*yaml.RNode) error {
if err := comments.CopyComments(in, node); err != nil {
return errors.Wrap(err)
}
if err := order.SyncOrder(in, node); err != nil {
return errors.Wrap(err)
}
if err := node.PipeE(yaml.ClearAnnotation(idAnnotation)); err != nil {
return errors.Wrap(err)
}

View File

@@ -19,6 +19,7 @@ require (
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15
gopkg.in/yaml.v2 v2.4.0
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e
sigs.k8s.io/yaml v1.2.0
)
// These can be removed after upgrading golangci-lint (Issue #3663)

View File

@@ -225,4 +225,5 @@ k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -6,6 +6,6 @@ This code is used by the starlark runtime. We copied it in to reduce the depende
## go-yaml/yaml
This code is used extensively by kyaml. It is a copy of upstream at a particular revision that kubectl is using, with [a change we need](https://github.com/go-yaml/yaml/pull/753) cherry-picked on top. For background information on this problem, see https://github.com/kubernetes-sigs/kustomize/issues/3946.
This code is used extensively by kyaml. It is a copy of upstream at a particular revision that kubectl is using, with fixes we need cherry-picked on top ([#753](https://github.com/go-yaml/yaml/pull/753)). For background information on this problem, see https://github.com/kubernetes-sigs/kustomize/issues/3946.
This copy was created using the [git subtree technique](https://medium.com/@porteneuve/mastering-git-subtrees-943d29a798ec) and can be recreated on top of a new version of go-yaml v3 using the [update-go-yaml.sh](update-go-yaml.sh) script. Please note that there is nothing special about the fork directory, so copy-paste with manual edits will work just fine if you prefer.
This copy was created using the [git subtree technique](https://medium.com/@porteneuve/mastering-git-subtrees-943d29a798ec) and can be recreated on top of a new version of go-yaml v3 using the [update-go-yaml.sh](update-go-yaml.sh) script. To add an additional go-yaml PR to be cherry-picked, simply update the script's `GO_YAML_PRS` variable. Please note that there is nothing special about the fork directory, so copy-paste with manual edits will work just fine if you prefer.

View File

@@ -18,13 +18,12 @@ blue=$(tput setaf 4)
normal=$(tput sgr0)
# This should be the version of go-yaml v3 used by kubectl
# In the original fork, this is 496545a6307b2a7d7a710fd516e5e16e8ab62dbc
export GOYAML_SHA=$1
export GOYAML_REF="goyaml-$GOYAML_SHA"
# In the original fork, this is 496545a6307b2a7d7a710fd516e5e16e8ab62dbc
export GOYAML_SHA=$1
export GOYAML_REF="goyaml-$GOYAML_SHA"
# The PRs we need to cherry-pick onto the above commit
declare -r GO_YAML_PR=753
declare -r KUSTOMIZE_PR=4004
declare -r GO_YAML_PRS=(753)
REPO_ROOT=$(git rev-parse --show-toplevel)
declare -r REPO_ROOT
@@ -77,8 +76,11 @@ function cherry-pick(){
subtree_commit_flag=""
explain "Removing the fork's tree from git, if it exists. We'll write over this commit in a moment, but \`read-tree\` requires a clean directory."
if [[ $(find kyaml/internal/forked/github.com/go-yaml/yaml -type f -delete) ]]; then
git commit --all -m "Temporarily remove go-yaml fork"
find kyaml/internal/forked/github.com/go-yaml/yaml -type f -delete
if [[ $(git diff --exit-code kyaml/internal/forked/github.com/go-yaml/yaml) ]]; then
git add kyaml/internal/forked/github.com/go-yaml/yaml
git commit -m "Temporarily remove go-yaml fork"
subtree_commit_flag="--amend"
fi
@@ -87,21 +89,20 @@ git fetch --depth=1 https://github.com/go-yaml/yaml.git "$GOYAML_SHA:$GOYAML_REF
explain "Inserting the content we just pulled as a subtree of this repository and squash the changes into the last commit."
git read-tree --prefix=kyaml/internal/forked/github.com/go-yaml/yaml/ -u "$GOYAML_REF"
git commit $subtree_commit_flag --all -m "Internal copy of go-yaml at $GOYAML_SHA"
git add kyaml/internal/forked/github.com/go-yaml/yaml
git commit $subtree_commit_flag -m "Internal copy of go-yaml at $GOYAML_SHA"
explain "Subtree creation successful."
explain "Cherry-picking the commits from our go-yaml/yaml PR"
cherry-pick https://github.com/go-yaml/yaml $GO_YAML_PR
explain "Cherry-picking the commits from our go-yaml/yaml PRs"
for pr in "${GO_YAML_PRS[@]}" ; do
cherry-pick https://github.com/go-yaml/yaml "$pr"
done
explain "Converting module to be internal."
find kyaml/internal/forked/github.com/go-yaml/yaml -name "*.go" -type f | xargs sed -i '' s+"gopkg.in/yaml.v3"+"sigs.k8s.io/kustomize/kyaml/internal/forked/github.com/go-yaml/yaml"+g
rm kyaml/internal/forked/github.com/go-yaml/yaml/go.mod
git commit --all -m "Internalize forked code"
# This is only necessary in the initial forking of the code
# explain "Cherry-picking the commits from our test fixes in Kustomize PR"
# cherry-pick https://github.com/kubernetes-sigs/kustomize $KUSTOMIZE_PR
explain "SUCCEEDED."
exit 0

View File

@@ -7,6 +7,7 @@ import (
"bytes"
"fmt"
"io"
"regexp"
"sort"
"strings"
@@ -36,6 +37,9 @@ type ByteReadWriter struct {
// the Resources, otherwise they will be cleared.
KeepReaderAnnotations bool
// PreserveSeqIndent if true adds kioutil.SeqIndentAnnotation to each resource
PreserveSeqIndent bool
// Style is a style that is set on the Resource Node Document.
Style yaml.Style
@@ -52,6 +56,7 @@ func (rw *ByteReadWriter) Read() ([]*yaml.RNode, error) {
b := &ByteReader{
Reader: rw.Reader,
OmitReaderAnnotations: rw.OmitReaderAnnotations,
PreserveSeqIndent: rw.PreserveSeqIndent,
}
val, err := b.Read()
if rw.FunctionConfig == nil {
@@ -108,9 +113,12 @@ type ByteReader struct {
Reader io.Reader
// OmitReaderAnnotations will configures Read to skip setting the config.kubernetes.io/index
// annotation on Resources as they are Read.
// and internal.config.kubernetes.io/seqindent annotations on Resources as they are Read.
OmitReaderAnnotations bool
// PreserveSeqIndent if true adds kioutil.SeqIndentAnnotation to each resource
PreserveSeqIndent bool
// SetAnnotations is a map of caller specified annotations to set on resources as they are read
// These are independent of the annotations controlled by OmitReaderAnnotations
SetAnnotations map[string]string
@@ -133,7 +141,42 @@ type ByteReader struct {
var _ Reader = &ByteReader{}
// splitDocuments returns a slice of all documents contained in a YAML string. Multiple documents can be divided by the
// YAML document separator (---). It allows for white space and comments to be after the separator on the same line,
// but will return an error if anything else is on the line.
func splitDocuments(s string) ([]string, error) {
docs := make([]string, 0)
if len(s) > 0 {
// The YAML document separator is any line that starts with ---
yamlSeparatorRegexp := regexp.MustCompile(`\n---.*\n`)
// Find all separators, check them for invalid content, and append each document to docs
separatorLocations := yamlSeparatorRegexp.FindAllStringIndex(s, -1)
prev := 0
for i := range separatorLocations {
loc := separatorLocations[i]
separator := s[loc[0]:loc[1]]
// If the next non-whitespace character on the line following the separator is not a comment, return an error
trimmedContentAfterSeparator := strings.TrimSpace(separator[4:])
if len(trimmedContentAfterSeparator) > 0 && trimmedContentAfterSeparator[0] != '#' {
return nil, errors.Errorf("invalid document separator: %s", strings.TrimSpace(separator))
}
docs = append(docs, s[prev:loc[0]])
prev = loc[1]
}
docs = append(docs, s[prev:])
}
return docs, nil
}
func (r *ByteReader) Read() ([]*yaml.RNode, error) {
if r.PreserveSeqIndent && r.OmitReaderAnnotations {
return nil, errors.Errorf(`"PreserveSeqIndent" option adds a reader annotation, please set "OmitReaderAnnotations" to false`)
}
output := ResourceNodeSlice{}
// by manually splitting resources -- otherwise the decoder will get the Resource
@@ -144,8 +187,12 @@ func (r *ByteReader) Read() ([]*yaml.RNode, error) {
return nil, errors.Wrap(err)
}
// replace the ending \r\n (line ending used in windows) with \n and then separate by \n---\n
values := strings.Split(strings.ReplaceAll(input.String(), "\r\n", "\n"), "\n---\n")
// Replace the ending \r\n (line ending used in windows) with \n and then split it into multiple YAML documents
// if it contains document separators (---)
values, err := splitDocuments(strings.ReplaceAll(input.String(), "\r\n", "\n"))
if err != nil {
return nil, errors.Wrap(err)
}
index := 0
for i := range values {
@@ -155,10 +202,11 @@ func (r *ByteReader) Read() ([]*yaml.RNode, error) {
values[i] += "\n"
}
decoder := yaml.NewDecoder(bytes.NewBufferString(values[i]))
node, err := r.decode(index, decoder)
node, err := r.decode(values[i], index, decoder)
if err == io.EOF {
continue
}
if err != nil {
return nil, errors.Wrap(err)
}
@@ -209,7 +257,7 @@ func (r *ByteReader) Read() ([]*yaml.RNode, error) {
return output, nil
}
func (r *ByteReader) decode(index int, decoder *yaml.Decoder) (*yaml.RNode, error) {
func (r *ByteReader) decode(originalYAML string, index int, decoder *yaml.Decoder) (*yaml.RNode, error) {
node := &yaml.Node{}
err := decoder.Decode(node)
if err == io.EOF {
@@ -232,6 +280,14 @@ func (r *ByteReader) decode(index int, decoder *yaml.Decoder) (*yaml.RNode, erro
}
if !r.OmitReaderAnnotations {
r.SetAnnotations[kioutil.IndexAnnotation] = fmt.Sprintf("%d", index)
if r.PreserveSeqIndent {
// derive and add the seqindent annotation
seqIndentStyle := yaml.DeriveSeqIndentStyle(originalYAML)
if seqIndentStyle != "" {
r.SetAnnotations[kioutil.SeqIndentAnnotation] = fmt.Sprintf("%s", seqIndentStyle)
}
}
}
var keys []string
for k := range r.SetAnnotations {

View File

@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert"
. "sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
)
func TestByteReader(t *testing.T) {
@@ -372,6 +373,62 @@ metadata:
},
instance: ByteReader{},
},
//
//
//
{
name: "white_space_after_document_separator_should_be_ignored",
input: `
a: b
---
c: d
`,
expectedItems: []string{
`
a: b
`,
`
c: d
`,
},
instance: ByteReader{OmitReaderAnnotations: true},
},
//
//
//
{
name: "comment_after_document_separator_should_be_ignored",
input: `
a: b
--- #foo
c: d
`,
expectedItems: []string{
`
a: b
`,
`
c: d
`,
},
instance: ByteReader{OmitReaderAnnotations: true},
},
//
//
//
{
name: "anything_after_document_separator_other_than_white_space_or_comment_is_an_error",
input: `
a: b
--- foo
c: d
`,
err: "invalid document separator: --- foo",
instance: ByteReader{OmitReaderAnnotations: true},
},
}
for i := range testCases {
@@ -749,3 +806,102 @@ items:
})
}
}
// TestByteReader_AddSeqIndentAnnotation tests if the internal.config.kubernetes.io/seqindent
// annotation is added to resources appropriately
func TestByteReader_AddSeqIndentAnnotation(t *testing.T) {
type testCase struct {
name string
err string
input string
expectedAnnoValue string
OmitReaderAnnotations bool
}
testCases := []testCase{
{
name: "read with wide indentation",
input: `apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
`,
expectedAnnoValue: "wide",
},
{
name: "read with compact indentation",
input: `apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
`,
expectedAnnoValue: "compact",
},
{
name: "read with mixed indentation, wide wins",
input: `apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
env:
- foo
- bar
`,
expectedAnnoValue: "wide",
},
{
name: "read with mixed indentation, compact wins",
input: `apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
env:
- foo
- bar
`,
expectedAnnoValue: "compact",
},
{
name: "error if conflicting options",
input: `apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
env:
- foo
- bar
`,
OmitReaderAnnotations: true,
err: `"PreserveSeqIndent" option adds a reader annotation, please set "OmitReaderAnnotations" to false`,
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
rNodes, err := (&ByteReader{
OmitReaderAnnotations: tc.OmitReaderAnnotations,
PreserveSeqIndent: true,
Reader: bytes.NewBuffer([]byte(tc.input)),
}).Read()
if tc.err != "" {
assert.Error(t, err)
assert.Equal(t, tc.err, err.Error())
return
}
assert.NoError(t, err)
actual := rNodes[0].GetAnnotations()[kioutil.SeqIndentAnnotation]
assert.Equal(t, tc.expectedAnnoValue, actual)
})
}
}

View File

@@ -324,3 +324,240 @@ functionConfig:
})
}
}
func TestByteReadWriter_RetainSeqIndent(t *testing.T) {
type testCase struct {
name string
err string
input string
expectedOutput string
instance kio.ByteReadWriter
}
testCases := []testCase{
{
name: "round_trip with 2 space seq indent",
input: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
---
apiVersion: v1
kind: Service
spec:
- foo
- bar
`,
expectedOutput: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
---
apiVersion: v1
kind: Service
spec:
- foo
- bar
`,
},
{
name: "round_trip with 0 space seq indent",
input: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
---
apiVersion: v1
kind: Service
spec:
- foo
- bar
`,
expectedOutput: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
---
apiVersion: v1
kind: Service
spec:
- foo
- bar
`,
},
{
name: "round_trip with different indentations",
input: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
---
apiVersion: v1
kind: Service
spec:
- foo
- bar
`,
expectedOutput: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
---
apiVersion: v1
kind: Service
spec:
- foo
- bar
`,
},
{
name: "round_trip with mixed indentations in same resource, wide wins as it is first",
input: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
env:
- foo
- bar
`,
expectedOutput: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
env:
- foo
- bar
`,
},
{
name: "round_trip with mixed indentations in same resource, compact wins as it is first",
input: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
env:
- foo
- bar
`,
expectedOutput: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
env:
- foo
- bar
`,
},
{
name: "unwrap ResourceList with annotations",
input: `
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- kind: Deployment
metadata:
annotations:
internal.config.kubernetes.io/seqindent: "compact"
spec:
- foo
- bar
- kind: Service
metadata:
annotations:
internal.config.kubernetes.io/seqindent: "wide"
spec:
- foo
- bar
`,
expectedOutput: `
kind: Deployment
spec:
- foo
- bar
---
kind: Service
spec:
- foo
- bar
`,
},
{
name: "round_trip with mixed indentations in same resource, wide wins as it is first",
input: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
env:
- foo
- bar
- baz
`,
expectedOutput: `
apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
env:
- foo
- bar
- baz
`,
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
var in, out bytes.Buffer
in.WriteString(tc.input)
w := tc.instance
w.Writer = &out
w.Reader = &in
w.PreserveSeqIndent = true
nodes, err := w.Read()
if !assert.NoError(t, err) {
t.FailNow()
}
w.WrappingKind = ""
err = w.Write(nodes)
if !assert.NoError(t, err) {
t.FailNow()
}
if tc.err != "" {
if !assert.EqualError(t, err, tc.err) {
t.FailNow()
}
return
}
if !assert.Equal(t,
strings.TrimSpace(tc.expectedOutput), strings.TrimSpace(out.String())) {
t.FailNow()
}
})
}
}

View File

@@ -62,6 +62,12 @@ func (w ByteWriter) Write(inputNodes []*yaml.RNode) error {
// Even though we use the this value further down we must check this before removing annotations
jsonEncodeSingleBareNode := w.shouldJSONEncodeSingleBareNode(nodes)
// store seqindent annotation value for each node in order to set the encoder indentation
var seqIndentsForNodes []string
for i := range nodes {
seqIndentsForNodes = append(seqIndentsForNodes, nodes[i].GetAnnotations()[kioutil.SeqIndentAnnotation])
}
for i := range nodes {
// clean resources by removing annotations set by the Reader
if !w.KeepReaderAnnotations {
@@ -69,6 +75,11 @@ func (w ByteWriter) Write(inputNodes []*yaml.RNode) error {
if err != nil {
return errors.Wrap(err)
}
_, err = nodes[i].Pipe(yaml.ClearAnnotation(kioutil.SeqIndentAnnotation))
if err != nil {
return errors.Wrap(err)
}
}
for _, a := range w.ClearAnnotations {
_, err := nodes[i].Pipe(yaml.ClearAnnotation(a))
@@ -97,8 +108,13 @@ func (w ByteWriter) Write(inputNodes []*yaml.RNode) error {
// don't wrap the elements
if w.WrappingKind == "" {
for i := range nodes {
if seqIndentsForNodes[i] == string(yaml.WideSequenceStyle) {
encoder.DefaultSeqIndent()
} else {
encoder.CompactSeqIndent()
}
if err := encoder.Encode(nodes[i].Document()); err != nil {
return err
return errors.Wrap(err)
}
}
return nil

View File

@@ -311,6 +311,67 @@ metadata:
`,
},
//
// Test Case
//
{
name: "keep_annotation_seqindent",
instance: ByteWriter{KeepReaderAnnotations: true},
items: []string{
`a: b #first
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/index: "compact"
`,
`e: f
g:
h:
- i # has a list
- j
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/b_test.yaml"
internal.config.kubernetes.io/seqindent: "wide"
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: 1
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/seqindent: "compact"
`,
},
expectedOutput: `a: b #first
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/index: "compact"
---
e: f
g:
h:
- i # has a list
- j
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/b_test.yaml"
internal.config.kubernetes.io/seqindent: "wide"
---
c: d # second
metadata:
annotations:
config.kubernetes.io/index: 1
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/seqindent: "compact"
`,
},
//
// Test Case
//
@@ -337,6 +398,34 @@ metadata:
}`,
},
//
// Test Case
//
{
name: "encode_valid_json_remove_seqindent_annotation",
items: []string{
`{
"a": "a long string that would certainly see a newline introduced by the YAML marshaller abcd123",
metadata: {
annotations: {
"internal.config.kubernetes.io/seqindent": "compact",
"config.kubernetes.io/index": "0",
"config.kubernetes.io/path": "test.json"
}
}
}`,
},
expectedOutput: `{
"a": "a long string that would certainly see a newline introduced by the YAML marshaller abcd123",
"metadata": {
"annotations": {
"config.kubernetes.io/path": "test.json"
}
}
}`,
},
//
// Test Case
//

View File

@@ -22,6 +22,9 @@ const (
// PathAnnotation records the path to the file the Resource was read from
PathAnnotation AnnotationKey = "config.kubernetes.io/path"
// SeqIndentAnnotation records the sequence nodes indentation of the input resource
SeqIndentAnnotation AnnotationKey = "internal.config.kubernetes.io/seqindent"
)
func GetFileAnnotations(rn *yaml.RNode) (string, string, error) {

View File

@@ -40,6 +40,9 @@ type LocalPackageReadWriter struct {
KeepReaderAnnotations bool `yaml:"keepReaderAnnotations,omitempty"`
// PreserveSeqIndent if true adds kioutil.SeqIndentAnnotation to each resource
PreserveSeqIndent bool
// PackagePath is the path to the package directory.
PackagePath string `yaml:"path,omitempty"`
@@ -86,6 +89,7 @@ func (r *LocalPackageReadWriter) Read() ([]*yaml.RNode, error) {
SetAnnotations: r.SetAnnotations,
PackageFileName: r.PackageFileName,
FileSkipFunc: r.FileSkipFunc,
PreserveSeqIndent: r.PreserveSeqIndent,
}.Read()
if err != nil {
return nil, errors.Wrap(err)
@@ -177,6 +181,9 @@ type LocalPackageReader struct {
// FileSkipFunc is a function which returns true if reader should ignore
// the file
FileSkipFunc LocalPackageSkipFileFunc
// PreserveSeqIndent if true adds kioutil.SeqIndentAnnotation to each resource
PreserveSeqIndent bool
}
var _ Reader = LocalPackageReader{}
@@ -267,6 +274,7 @@ func (r *LocalPackageReader) readFile(path string, _ os.FileInfo) ([]*yaml.RNode
Reader: f,
OmitReaderAnnotations: r.OmitReaderAnnotations,
SetAnnotations: r.SetAnnotations,
PreserveSeqIndent: r.PreserveSeqIndent,
}
return rr.Read()
}

View File

@@ -337,6 +337,69 @@ g:
}
}
func TestLocalPackageReader_Read_PreserveSeqIndent(t *testing.T) {
s := SetupDirectories(t, filepath.Join("a", "b"), filepath.Join("a", "c"))
defer s.Clean()
s.WriteFile(t, filepath.Join("a_test.yaml"), readFileA)
s.WriteFile(t, filepath.Join("b_test.yaml"), readFileB)
paths := []struct {
path string
}{
{path: "./"},
{path: s.Root},
}
for _, p := range paths {
// empty path
rfr := LocalPackageReader{PackagePath: p.path, PreserveSeqIndent: true}
nodes, err := rfr.Read()
if !assert.NoError(t, err) {
return
}
if !assert.Len(t, nodes, 3) {
return
}
expected := []string{
`a: b #first
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'a_test.yaml'
internal.config.kubernetes.io/seqindent: 'compact'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'a_test.yaml'
internal.config.kubernetes.io/seqindent: 'compact'
`,
`# second thing
e: f
g:
h:
- i # has a list
- j
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'b_test.yaml'
internal.config.kubernetes.io/seqindent: 'compact'
`,
}
for i := range nodes {
val, err := nodes[i].String()
if !assert.NoError(t, err) {
return
}
if !assert.Equal(t, expected[i], val) {
return
}
}
}
}
func TestLocalPackageReader_Read_nestedDirs(t *testing.T) {
s := SetupDirectories(t, filepath.Join("a", "b"), filepath.Join("a", "c"))
defer s.Clean()

View File

@@ -16,6 +16,7 @@ import (
"sigs.k8s.io/kustomize/kyaml/openapi/kubernetesapi"
"sigs.k8s.io/kustomize/kyaml/openapi/kustomizationapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
k8syaml "sigs.k8s.io/yaml"
)
// globalSchema contains global state information about the openapi
@@ -536,7 +537,14 @@ func parseBuiltinSchema(version string) {
// parse parses and indexes a single json schema
func parse(b []byte) error {
var swagger spec.Swagger
s := string(b)
if len(s) > 0 && s[0] != '{' {
var err error
b, err = k8syaml.YAMLToJSON(b)
if err != nil {
return errors.Wrap(err)
}
}
if err := swagger.UnmarshalJSON(b); err != nil {
return errors.Wrap(err)
}

122
kyaml/order/syncorder.go Normal file
View File

@@ -0,0 +1,122 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package order
import (
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// SyncOrder recursively sorts the map node keys in 'to' node to match the order of
// map node keys in 'from' node at same tree depth, additional keys are moved to the end
// Field order might be altered due to round-tripping in arbitrary functions.
// This functionality helps to retain the original order of fields to avoid unnecessary diffs.
func SyncOrder(from, to *yaml.RNode) error {
if err := syncOrder(from, to); err != nil {
return errors.Errorf("failed to sync field order: %q", err.Error())
}
rearrangeHeadCommentOfSeqNode(to.YNode())
return nil
}
func syncOrder(from, to *yaml.RNode) error {
if from.IsNilOrEmpty() || to.IsNilOrEmpty() {
return nil
}
switch from.YNode().Kind {
case yaml.DocumentNode:
// Traverse the child of the documents
return syncOrder(yaml.NewRNode(from.YNode()), yaml.NewRNode(to.YNode()))
case yaml.MappingNode:
return VisitFields(from, to, func(fNode, tNode *yaml.MapNode) error {
// Traverse each field value
if fNode == nil || tNode == nil {
return nil
}
return syncOrder(fNode.Value, tNode.Value)
})
case yaml.SequenceNode:
return VisitElements(from, to, func(fNode, tNode *yaml.RNode) error {
// Traverse each list element
return syncOrder(fNode, tNode)
})
}
return nil
}
// VisitElements calls fn for each element in a SequenceNode.
// Returns an error for non-SequenceNodes
func VisitElements(from, to *yaml.RNode, fn func(fNode, tNode *yaml.RNode) error) error {
fElements, err := from.Elements()
if err != nil {
return errors.Wrap(err)
}
tElements, err := to.Elements()
if err != nil {
return errors.Wrap(err)
}
for i := range fElements {
if i >= len(tElements) {
return nil
}
if err := fn(fElements[i], tElements[i]); err != nil {
return errors.Wrap(err)
}
}
return nil
}
// VisitFields calls fn for each field in the RNode.
// Returns an error for non-MappingNodes.
func VisitFields(from, to *yaml.RNode, fn func(fNode, tNode *yaml.MapNode) error) error {
srcFieldNames, err := from.Fields()
if err != nil {
return nil
}
yaml.SyncMapNodesOrder(from, to)
// visit each field
for _, fieldName := range srcFieldNames {
if err := fn(from.Field(fieldName), to.Field(fieldName)); err != nil {
return errors.Wrap(err)
}
}
return nil
}
// rearrangeHeadCommentOfSeqNode addresses a remote corner case due to moving a
// map node in a sequence node with a head comment to the top
func rearrangeHeadCommentOfSeqNode(node *yaml.Node) {
if node == nil {
return
}
switch node.Kind {
case yaml.DocumentNode:
for _, node := range node.Content {
rearrangeHeadCommentOfSeqNode(node)
}
case yaml.MappingNode:
for _, node := range node.Content {
rearrangeHeadCommentOfSeqNode(node)
}
case yaml.SequenceNode:
for _, node := range node.Content {
// for each child mapping node, transfer the head comment of it's
// first child scalar node to the head comment of itself
if len(node.Content) > 0 && node.Content[0].Kind == yaml.ScalarNode {
if node.HeadComment == "" {
node.HeadComment = node.Content[0].HeadComment
continue
}
if node.Content[0].HeadComment != "" {
node.HeadComment += "\n" + node.Content[0].HeadComment
node.Content[0].HeadComment = ""
}
}
}
}
}

View File

@@ -0,0 +1,397 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package order
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
func TestSyncOrder(t *testing.T) {
testCases := []struct {
name string
from string
to string
expected string
}{
{
name: "sort data fields configmap with comments",
from: `apiVersion: v1
kind: ConfigMap
metadata:
name: setters-config
data:
# This should be the name of your Config Controller instance
cluster-name: cluster-name
# This should be the project where you deployed Config Controller
project-id: project-id # pro
project-number: "1234567890123"
# You can leave these defaults
namespace: config-control
deployment-repo: deployment-repo
source-repo: source-repo
`,
to: `apiVersion: v1
kind: ConfigMap
metadata: # kpt-merge: /setters-config
name: setters-config
data:
# You can leave these defaults
namespace: config-control
# This should be the name of your Config Controller instance
cluster-name: cluster-name
deployment-repo: deployment-repo
# This should be the project where you deployed Config Controller
project-id: project-id # project
project-number: "1234567890123"
source-repo: source-repo
`,
expected: `apiVersion: v1
kind: ConfigMap
metadata: # kpt-merge: /setters-config
name: setters-config
data:
# This should be the name of your Config Controller instance
cluster-name: cluster-name
# This should be the project where you deployed Config Controller
project-id: project-id # project
project-number: "1234567890123"
# You can leave these defaults
namespace: config-control
deployment-repo: deployment-repo
source-repo: source-repo
`,
},
{
name: "sort data fields configmap but retain order of extra fields",
from: `apiVersion: v1
kind: ConfigMap
data:
baz: bar
cluster-name: cluster-name
foo: config-control
`,
to: `kind: ConfigMap
apiVersion: v1
metadata:
name: foo
data:
color: orange
foo: config-control
abc: def
cluster-name: cluster-name
`,
expected: `apiVersion: v1
kind: ConfigMap
data:
cluster-name: cluster-name
foo: config-control
color: orange
abc: def
metadata:
name: foo
`,
},
{
name: "sort containers list node with sequence head comments preservation",
from: `apiVersion: apps/v1
kind: Deployment
metadata:
name: before
spec:
containers:
- name: nginx
# nginx image
image: "nginx:1.16.1"
ports:
- protocol: TCP # tcp protocol
containerPort: 80
`,
to: `apiVersion: apps/v1
kind: Deployment
metadata:
name: after
spec:
containers:
# ports comment
- ports:
- containerPort: 80
protocol: TCP # tcp protocol
# nginx image
image: "nginx:1.16.2"
# nginx container
name: nginx
# end of resource
`,
expected: `apiVersion: apps/v1
kind: Deployment
metadata:
name: after
spec:
containers:
# ports comment
# nginx container
- name: nginx
# nginx image
image: "nginx:1.16.2"
ports:
- protocol: TCP # tcp protocol
containerPort: 80
# end of resource
`,
},
{
name: "Do not alter sequence order",
from: `apiVersion: v1
kind: KRMFile
metadata:
name: before
pipeline:
mutators:
- image: apply-setters:v0.1
configPath: setters.yaml
- image: set-namespace:v0.1
configPath: ns.yaml
`,
to: `apiVersion: v1
kind: KRMFile
metadata:
name: after
pipeline:
mutators:
- configPath: sr.yaml
image: search-replace:v0.1
- image: apply-setters:v0.1
configPath: setters.yaml
- image: set-namespace:v0.1
configPath: ns.yaml
`,
expected: `apiVersion: v1
kind: KRMFile
metadata:
name: after
pipeline:
mutators:
- image: search-replace:v0.1
configPath: sr.yaml
- image: apply-setters:v0.1
configPath: setters.yaml
- image: set-namespace:v0.1
configPath: ns.yaml
`,
},
{
name: "Complex ASM reorder example",
from: `apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: (unknown)
creationTimestamp: null
name: controlplanerevisions.mesh.cloud.google.com
spec:
group: mesh.cloud.google.com
names:
kind: ControlPlaneRevision
listKind: ControlPlaneRevisionList
plural: controlplanerevisions
singular: controlplanerevision
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
description: ControlPlaneRevision is the Schema for the ControlPlaneRevision API
properties:
apiVersion:
description: 'APIVersion'
type: string
kind:
description: 'Kind'
type: string
metadata:
type: object
spec:
description: ControlPlaneRevisionSpec defines the desired state of ControlPlaneRevision
properties:
channel:
description: ReleaseChannel determines the aggressiveness of upgrades.
enum:
- regular
- rapid
- stable
type: string
type:
description: ControlPlaneRevisionType determines how the revision should be managed.
enum:
- managed_service
type: string
type: object
status:
description: ControlPlaneRevisionStatus defines the observed state of ControlPlaneRevision.
properties:
conditions:
items:
description: ControlPlaneRevisionCondition is a repeated struct definining the current conditions of a ControlPlaneRevision.
properties:
lastTransitionTime:
description: Last time the condition transitioned from one status to another
format: date-time
type: string
message:
description: Human-readable message indicating details about last transition
type: string
reason:
description: Unique, one-word, CamelCase reason for the condition's last transition
type: string
status:
description: Status is the status of the condition. Can be True, False, or Unknown.
type: string
type:
description: Type is the type of the condition.
type: string
type: object
type: array
type: object
type: object
version: v1alpha1
versions:
- name: v1alpha1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
`,
to: `apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: controlplanerevisions.mesh.cloud.google.com
annotations:
controller-gen.kubebuilder.io/version: (unknown)
creationTimestamp: null
spec:
group: mesh.cloud.google.com
names:
kind: ControlPlaneRevision
listKind: ControlPlaneRevisionList
plural: controlplanerevisions
singular: controlplanerevision
scope: Namespaced
subresources:
status: {}
validation:
openAPIV3Schema:
type: object
description: ControlPlaneRevision is the Schema for the ControlPlaneRevision API
properties:
apiVersion:
type: string
description: 'APIVersion'
kind:
type: string
description: 'Kind'
metadata:
type: object
spec:
type: object
description: ControlPlaneRevisionSpec defines the desired state of ControlPlaneRevision
properties:
type:
type: string
description: ControlPlaneRevisionType determines how the revision should be managed.
enum:
- managed_service
channel:
type: string
description: ReleaseChannel determines the aggressiveness of upgrades.
enum:
- regular
- rapid
- stable
status:
type: object
description: ControlPlaneRevisionStatus defines the observed state of ControlPlaneRevision.
properties:
conditions:
type: array
items:
type: object
description: ControlPlaneRevisionCondition is a repeated struct definining the current conditions of a ControlPlaneRevision.
properties:
type:
type: string
description: Type is the type of the condition.
status:
type: string
description: Status is the status of the condition. Can be True, False, or Unknown.
lastTransitionTime:
type: string
description: Last time the condition transitioned from one status to another
format: date-time
message:
type: string
description: Human-readable message indicating details about last transition
reason:
type: string
description: Unique, one-word, CamelCase reason for the condition's last transition
version: v1alpha1
versions:
- name: v1alpha1
served: true
storage: true
status:
acceptedNames:
kind: ""
plural: ""
conditions: []
storedVersions: []
`,
expected: `test.from`,
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
from, err := yaml.Parse(tc.from)
if !assert.NoError(t, err) {
t.FailNow()
}
to, err := yaml.Parse(tc.to)
if !assert.NoError(t, err) {
t.FailNow()
}
err = SyncOrder(from, to)
if !assert.NoError(t, err) {
t.FailNow()
}
out := &bytes.Buffer{}
kio.ByteWriter{
Writer: out,
KeepReaderAnnotations: false,
}.Write([]*yaml.RNode{to})
// this means "to" is just a reordered version of "from" and after syncing order,
// resultant "to" must be equal to "from"
if tc.expected == "test.from" {
tc.expected = tc.from
}
if !assert.Equal(t, tc.expected, out.String()) {
t.FailNow()
}
})
}
}

25
kyaml/sliceutil/slice.go Normal file
View File

@@ -0,0 +1,25 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package sliceutil
// Contains return true if string e is present in slice s
func Contains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}
// Remove removes the first occurrence of r in slice s
// and returns remaining slice
func Remove(s []string, r string) []string {
for i, v := range s {
if v == r {
return append(s[:i], s[i+1:]...)
}
}
return s
}

View File

@@ -0,0 +1,25 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package sliceutil
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestContains(t *testing.T) {
assert.True(t, Contains([]string{"foo", "bar"}, "bar"))
assert.False(t, Contains([]string{"foo", "bar"}, "baz"))
assert.False(t, Contains([]string{}, "bar"))
assert.False(t, Contains([]string{}, ""))
}
func TestRemove(t *testing.T) {
assert.Equal(t, Remove([]string{"foo", "bar"}, "bar"), []string{"foo"})
assert.Equal(t, Remove([]string{"foo", "bar", "foo"}, "foo"), []string{"bar", "foo"})
assert.Equal(t, Remove([]string{"foo"}, "foo"), []string{})
assert.Equal(t, Remove([]string{}, "foo"), []string{})
assert.Equal(t, Remove([]string{"foo", "bar", "foo"}, "baz"), []string{"foo", "bar", "foo"})
}

View File

@@ -10,14 +10,21 @@ import (
"sigs.k8s.io/kustomize/kyaml/internal/forked/github.com/go-yaml/yaml"
)
const CompactSequenceStyle = "compact"
const WideSequenceStyle = "wide"
const (
WideSequenceStyle SequenceIndentStyle = "wide"
CompactSequenceStyle SequenceIndentStyle = "compact"
DefaultIndent = 2
)
const DefaultIndent = 2
const DefaultSequenceStyle = CompactSequenceStyle
// SeqIndentType holds the indentation style for sequence nodes
type SequenceIndentStyle string
var sequenceIndentationStyle = DefaultSequenceStyle
var indent = DefaultIndent
// EncoderOptions are options that can be used to configure the encoder,
// do not expose new options without considerable justification
type EncoderOptions struct {
// SeqIndent is the indentation style for YAML Sequence nodes
SeqIndent SequenceIndentStyle
}
// Expose the yaml.v3 functions so this package can be used as a replacement
@@ -43,13 +50,33 @@ var Unmarshal = yaml.Unmarshal
var NewDecoder = yaml.NewDecoder
var NewEncoder = func(w io.Writer) *yaml.Encoder {
e := yaml.NewEncoder(w)
e.SetIndent(indent)
if sequenceIndentationStyle == CompactSequenceStyle {
e.CompactSeqIndent()
}
e.SetIndent(DefaultIndent)
e.CompactSeqIndent()
return e
}
// MarshalWithOptions marshals the input interface with provided options
func MarshalWithOptions(in interface{}, opts *EncoderOptions) ([]byte, error) {
var buf bytes.Buffer
err := NewEncoderWithOptions(&buf, opts).Encode(in)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// NewEncoderWithOptions returns the encoder with provided options
func NewEncoderWithOptions(w io.Writer, opts *EncoderOptions) *yaml.Encoder {
encoder := NewEncoder(w)
encoder.SetIndent(DefaultIndent)
if opts.SeqIndent == WideSequenceStyle {
encoder.DefaultSeqIndent()
} else {
encoder.CompactSeqIndent()
}
return encoder
}
var AliasNode yaml.Kind = yaml.AliasNode
var DocumentNode yaml.Kind = yaml.DocumentNode
var MappingNode yaml.Kind = yaml.MappingNode

View File

@@ -14,6 +14,7 @@ import (
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/internal/forked/github.com/go-yaml/yaml"
"sigs.k8s.io/kustomize/kyaml/sliceutil"
"sigs.k8s.io/kustomize/kyaml/yaml/internal/k8sgen/pkg/labels"
)
@@ -146,6 +147,50 @@ func NewMapRNode(values *map[string]string) *RNode {
return m
}
// SyncMapNodesOrder sorts the map node keys in 'to' node to match the order of
// map node keys in 'from' node, additional keys are moved to the end
func SyncMapNodesOrder(from, to *RNode) {
to.Copy()
res := &RNode{value: &yaml.Node{
Kind: to.YNode().Kind,
Style: to.YNode().Style,
Tag: to.YNode().Tag,
Anchor: to.YNode().Anchor,
Alias: to.YNode().Alias,
HeadComment: to.YNode().HeadComment,
LineComment: to.YNode().LineComment,
FootComment: to.YNode().FootComment,
Line: to.YNode().Line,
Column: to.YNode().Column,
}}
fromFieldNames, err := from.Fields()
if err != nil {
return
}
toFieldNames, err := to.Fields()
if err != nil {
return
}
for _, fieldName := range fromFieldNames {
if !sliceutil.Contains(toFieldNames, fieldName) {
continue
}
// append the common nodes in the order defined in 'from' node
res.value.Content = append(res.value.Content, to.Field(fieldName).Key.YNode(), to.Field(fieldName).Value.YNode())
toFieldNames = sliceutil.Remove(toFieldNames, fieldName)
}
for _, fieldName := range toFieldNames {
// append the residual nodes which are not present in 'from' node
res.value.Content = append(res.value.Content, to.Field(fieldName).Key.YNode(), to.Field(fieldName).Value.YNode())
}
to.SetYNode(res.YNode())
}
// NewRNode returns a new RNode pointer containing the provided Node.
func NewRNode(value *yaml.Node) *RNode {
return &RNode{value: value}

View File

@@ -139,16 +139,16 @@ type NameMeta struct {
type ResourceMeta struct {
TypeMeta `json:",inline" yaml:",inline"`
// ObjectMeta is the metadata field of a Resource
ObjectMeta `yaml:"metadata,omitempty"`
ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"`
}
// ObjectMeta contains metadata about a Resource
type ObjectMeta struct {
NameMeta `json:",inline" yaml:",inline"`
// Labels is the metadata.labels field of a Resource
Labels map[string]string `yaml:"labels,omitempty"`
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
// Annotations is the metadata.annotations field of a Resource.
Annotations map[string]string `yaml:"annotations,omitempty"`
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
}
// GetIdentifier returns a ResourceIdentifier that includes

71
kyaml/yaml/util.go Normal file
View File

@@ -0,0 +1,71 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"strings"
)
// DeriveSeqIndentStyle derives the sequence indentation annotation value for the resource,
// originalYAML is the input yaml string,
// the style is decided by deriving the existing sequence indentation of first sequence node
func DeriveSeqIndentStyle(originalYAML string) string {
lines := strings.Split(originalYAML, "\n")
for i, line := range lines {
elems := strings.SplitN(line, "- ", 2)
if len(elems) != 2 {
continue
}
// prefix of "- " must be sequence of spaces
if strings.Trim(elems[0], " ") != "" {
continue
}
numSpacesBeforeSeqElem := len(elems[0])
// keyLine is the line before the first sequence element
keyLine := keyLineBeforeSeqElem(lines, i)
if keyLine == "" {
// there is no keyLine for this sequence node
// all of those lines are comments
continue
}
numSpacesBeforeKeyElem := len(keyLine) - len(strings.TrimLeft(keyLine, " "))
trimmedKeyLine := strings.Trim(keyLine, " ")
if strings.Count(trimmedKeyLine, ":") != 1 || !strings.HasSuffix(trimmedKeyLine, ":") {
// if the key line doesn't contain only one : that too at the end,
// this is not a sequence node, it is a wrapped sequence node string
// ignore it
continue
}
if numSpacesBeforeSeqElem == numSpacesBeforeKeyElem {
return string(CompactSequenceStyle)
}
if numSpacesBeforeSeqElem-numSpacesBeforeKeyElem == 2 {
return string(WideSequenceStyle)
}
}
return string(CompactSequenceStyle)
}
// keyLineBeforeSeqElem iterates through the lines before the first seqElement
// and tries to find the non-comment key line for the sequence node
func keyLineBeforeSeqElem(lines []string, seqElemIndex int) string {
// start with the previous line of sequence element
i := seqElemIndex - 1
for ; i >= 0; i-- {
line := lines[i]
trimmedLine := strings.Trim(line, " ")
if strings.HasPrefix(trimmedLine, "#") { // commented line
continue
}
// we have a non-commented line which can have a trailing comment
parts := strings.SplitN(line, "#", 2)
return parts[0] // throw away the trailing comment part
}
return ""
}

196
kyaml/yaml/util_test.go Normal file
View File

@@ -0,0 +1,196 @@
// Copyright 2021 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDeriveSeqIndentStyle(t *testing.T) {
type testCase struct {
name string
input string
expectedOutput string
}
testCases := []testCase{
{
name: "detect simple wide indent",
input: `apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
`,
expectedOutput: `wide`,
},
{
name: "detect simple compact indent",
input: `apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
`,
expectedOutput: `compact`,
},
{
name: "read with mixed indentation, wide first",
input: `apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
env:
- foo
- bar
`,
expectedOutput: `wide`,
},
{
name: "read with mixed indentation, compact first",
input: `apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
- baz
env:
- foo
- bar
`,
expectedOutput: `compact`,
},
{
name: "read with mixed indentation, compact first with less elements",
input: `apiVersion: apps/v1
kind: Deployment
spec:
- foo
- bar
env:
- foo
- bar
- baz
`,
expectedOutput: `compact`,
},
{
name: "skip wrapped sequence strings, pipe hyphen",
input: `apiVersion: apps/v1
kind: Deployment
spec: |-
- foo
- bar
`,
expectedOutput: `compact`,
},
{
name: "skip wrapped sequence strings, pipe",
input: `apiVersion: apps/v1
kind: Deployment
spec: |
- foo
- bar
`,
expectedOutput: `compact`,
},
{
name: "skip wrapped sequence strings, right angle bracket",
input: `apiVersion: apps/v1
kind: Deployment
spec: >
- foo
- bar
`,
expectedOutput: `compact`,
},
{
name: "skip wrapped sequence strings, plus",
input: `apiVersion: apps/v1
kind: Deployment
spec: +
- foo
- bar
`,
expectedOutput: `compact`,
},
{
name: "handle comments",
input: `apiVersion: v1
kind: Service
spec:
ports: # comment 1
# comment 2
- name: etcd-server-ssl
port: 2380
# comment 3
- name: etcd-client-ssl
port: 2379
`,
expectedOutput: `wide`,
},
{
name: "nested wide vs compact",
input: `apiVersion: apps/v1
kind: Deployment
spec:
foo:
bar:
baz:
bor:
- a
- b
abc:
- a
- b
`,
expectedOutput: `wide`,
},
{
name: "invalid resource but valid yaml sequence",
input: ` - foo`,
expectedOutput: `compact`,
},
{
name: "invalid resource but valid yaml sequence with comments",
input: `
# comment 1
# comment 2
- foo
`,
expectedOutput: `compact`,
},
{
name: "- within sequence element",
input: `apiVersion: apps/v1
kind: Deployment
spec:
foo:
- - a`,
expectedOutput: `wide`,
},
{
name: "- within non sequence element",
input: `apiVersion: apps/v1
kind: Deployment
spec:
foo:
a: - b`,
expectedOutput: `compact`,
},
}
for i := range testCases {
tc := testCases[i]
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expectedOutput, DeriveSeqIndentStyle(tc.input))
})
}
}

View File

@@ -1,4 +1,4 @@
module sigs.k8s.io/kustomize/plugin/builtin/configmapgenerator
module sigs.k8s.io/kustomize/plugin/builtin/iampolicypgenerator
go 1.16

View File

@@ -63,11 +63,11 @@ apiVersion: v1
kind: Service
metadata:
annotations:
config.kubernetes.io/prefixes: baked-
config.kubernetes.io/previousKinds: Service
config.kubernetes.io/previousNames: apple
config.kubernetes.io/previousNamespaces: default
config.kubernetes.io/suffixes: -pie
internal.config.kubernetes.io/prefixes: baked-
internal.config.kubernetes.io/previousKinds: Service
internal.config.kubernetes.io/previousNames: apple
internal.config.kubernetes.io/previousNamespaces: default
internal.config.kubernetes.io/suffixes: -pie
name: baked-apple-pie
spec:
ports:
@@ -87,11 +87,11 @@ apiVersion: v1
kind: ConfigMap
metadata:
annotations:
config.kubernetes.io/prefixes: baked-
config.kubernetes.io/previousKinds: ConfigMap
config.kubernetes.io/previousNames: cm
config.kubernetes.io/previousNamespaces: default
config.kubernetes.io/suffixes: -pie
internal.config.kubernetes.io/prefixes: baked-
internal.config.kubernetes.io/previousKinds: ConfigMap
internal.config.kubernetes.io/previousNames: cm
internal.config.kubernetes.io/previousNamespaces: default
internal.config.kubernetes.io/suffixes: -pie
name: baked-cm-pie
`)
@@ -139,10 +139,10 @@ apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
config.kubernetes.io/prefixes: test-
config.kubernetes.io/previousKinds: Deployment
config.kubernetes.io/previousNames: deployment
config.kubernetes.io/previousNamespaces: default
internal.config.kubernetes.io/prefixes: test-
internal.config.kubernetes.io/previousKinds: Deployment
internal.config.kubernetes.io/previousNames: deployment
internal.config.kubernetes.io/previousNamespaces: default
name: test-deployment
spec:
template:

View File

@@ -4,7 +4,7 @@ go 1.16
require (
sigs.k8s.io/kustomize/api v0.8.9
sigs.k8s.io/kustomize/kyaml v0.10.20
sigs.k8s.io/kustomize/kyaml v0.11.0
sigs.k8s.io/yaml v1.2.0
)

View File

@@ -4,7 +4,7 @@ go 1.16
require (
sigs.k8s.io/kustomize/api v0.8.9
sigs.k8s.io/kustomize/kyaml v0.10.20
sigs.k8s.io/kustomize/kyaml v0.11.0
sigs.k8s.io/yaml v1.2.0
)

View File

@@ -14,7 +14,7 @@ func TestBashedConfigMapPlugin(t *testing.T) {
PrepExecPlugin("someteam.example.com", "v1", "BashedConfigMap")
defer th.Reset()
m := th.LoadAndRunGenerator(`
m := th.LoadAndRunGeneratorWithBuildAnnotations(`
apiVersion: someteam.example.com/v1
kind: BashedConfigMap
metadata:
@@ -28,6 +28,9 @@ data:
username: alice
kind: ConfigMap
metadata:
annotations:
internal.config.kubernetes.io/generatorOptions: |
options: {}
name: example-configmap-test
`)
if m.Resources()[0].NeedHashSuffix() != true {

View File

@@ -72,8 +72,9 @@ if [[ "$module" == "kustomize" || "$module" == "pluginator" ]]; then
skipBuild=false
fi
configFile=$(mktemp)
cat <<EOF >$configFile
goReleaserConfigFile=$(mktemp)
cat <<EOF >$goReleaserConfigFile
project_name: $module
archives:
@@ -91,7 +92,6 @@ builds:
goos:
- linux
- darwin
- windows
goarch:
- amd64
@@ -112,14 +112,14 @@ release:
EOF
cat $configFile
cat $goReleaserConfigFile
date
time /usr/local/bin/goreleaser release \
--timeout 10m \
--parallelism 4 \
--config=$configFile \
--config=$goReleaserConfigFile \
--release-notes=$changeLogFile \
--rm-dist \
--skip-validate $remainingArgs

View File

@@ -36,7 +36,7 @@ steps:
# Run goreleaser indirectly via a shell script
# to configure it properly.
- name: goreleaser/goreleaser:v0.155.0
- name: goreleaser/goreleaser:v0.172.1
timeout: 12m
entrypoint: /bin/sh
dir: myClone

View File

@@ -29,14 +29,17 @@ cp releasing/cloudbuild.yaml $config
# in the kustomize/dist directory.
sed -i "s|# - '--snapshot|- '--snapshot|" $config
echo "Executing cloud-build-local with:"
echo "Executing cloud-build-local with config file $config :"
echo "========================="
cat $config
echo "========================="
workspace=~/cloud-build-local-workspace
cloud-build-local \
--config=$config \
--substitutions=TAG_NAME=$1 \
--write-workspace=$workspace \
--dryrun=false \
.
@@ -47,3 +50,5 @@ echo "Result of local build:"
echo "##########################################"
tree ./$module/dist
echo "##########################################"
tree ./$workspace
echo "##########################################"