mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-14 10:30:59 +00:00
Compare commits
61 Commits
kyaml/v0.1
...
kustomize-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8ffc725c7 | ||
|
|
76f1411922 | ||
|
|
d1003d6f8f | ||
|
|
91f74e8d16 | ||
|
|
94c5096a95 | ||
|
|
f35aeb6a8e | ||
|
|
d6ce846047 | ||
|
|
ec069e4f19 | ||
|
|
c5adafd9ce | ||
|
|
16dcc98cff | ||
|
|
59c410a70a | ||
|
|
35d1c3f9b4 | ||
|
|
e17785af21 | ||
|
|
0537b59f27 | ||
|
|
339e33d2f3 | ||
|
|
f082ac02cf | ||
|
|
9538ae1258 | ||
|
|
34981b664f | ||
|
|
477d8930e0 | ||
|
|
b5091a566a | ||
|
|
9981c45554 | ||
|
|
0f736ec7fd | ||
|
|
7826ad1e06 | ||
|
|
f4e6816338 | ||
|
|
4a13725678 | ||
|
|
ab9b010856 | ||
|
|
29be7fabe4 | ||
|
|
74e867833a | ||
|
|
91dc6d2a0f | ||
|
|
3c1fd0e9cf | ||
|
|
4deeb7d59b | ||
|
|
89b12cfc62 | ||
|
|
c07ffa5c1e | ||
|
|
259fcfcef8 | ||
|
|
f81201b74d | ||
|
|
6dbc74b32e | ||
|
|
ed38b5fe2b | ||
|
|
a84badb834 | ||
|
|
e1804cbc76 | ||
|
|
d13eef7951 | ||
|
|
0b4c6baf44 | ||
|
|
b3af54340c | ||
|
|
8c14b9d1af | ||
|
|
d818ccae92 | ||
|
|
4cea8b9785 | ||
|
|
84a36801e0 | ||
|
|
6eb7b3508d | ||
|
|
2a5f4ac7d7 | ||
|
|
518a16d3ac | ||
|
|
d53a2ad45d | ||
|
|
bb02a7645b | ||
|
|
5a9d90c872 | ||
|
|
4fd7269643 | ||
|
|
1eb3c1a075 | ||
|
|
a1746f2f8c | ||
|
|
b727febd08 | ||
|
|
c819d69ae4 | ||
|
|
bb6f83fb96 | ||
|
|
02d14d724a | ||
|
|
aa92d83d8c | ||
|
|
78737f5a38 |
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
75
api/krusty/testdata/customschema.yaml
vendored
Normal 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
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
11
cmd/config/docs/api-conventions/manifest-annotations.md
Normal file
11
cmd/config/docs/api-conventions/manifest-annotations.md
Normal 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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 '-'")
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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", "",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# Copyright 2019 The Kubernetes Authors.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
apiVersion: example.com/v1alpha1
|
||||
kind: Demo
|
||||
spec:
|
||||
value: a
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
122
kyaml/order/syncorder.go
Normal 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 = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
397
kyaml/order/syncorder_test.go
Normal file
397
kyaml/order/syncorder_test.go
Normal 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
25
kyaml/sliceutil/slice.go
Normal 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
|
||||
}
|
||||
25
kyaml/sliceutil/sliceutil_test.go
Normal file
25
kyaml/sliceutil/sliceutil_test.go
Normal 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"})
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
71
kyaml/yaml/util.go
Normal 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
196
kyaml/yaml/util_test.go
Normal 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))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
module sigs.k8s.io/kustomize/plugin/builtin/configmapgenerator
|
||||
module sigs.k8s.io/kustomize/plugin/builtin/iampolicypgenerator
|
||||
|
||||
go 1.16
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "##########################################"
|
||||
|
||||
Reference in New Issue
Block a user