Compare commits

...

53 Commits

Author SHA1 Message Date
Kubernetes Prow Robot
0889995a61 Merge pull request #4198 from KnVerey/pinToCmdConfig
Pin to cmd/config v0.10.1
2021-09-24 16:34:21 -07:00
Katrina Verey
4e476ae574 Pin to cmd/config v0.10.1 2021-09-24 16:07:21 -07:00
Kubernetes Prow Robot
7efd7d23fe Merge pull request #4197 from KnVerey/pinToKyaml
Pin to kyaml v0.12.0
2021-09-24 14:07:50 -07:00
Katrina Verey
6fb944815b Pin to kyaml v0.12.0 2021-09-24 12:23:09 -07:00
Kubernetes Prow Robot
5e3432fbbe Merge pull request #4196 from natasha41575/test
fix tests for reader annotations
2021-09-24 08:31:24 -07:00
natasha41575
f0c6bd7773 fix tests for reader annotations 2021-09-24 08:20:44 -07:00
Jeff Regan
dd579c905d Merge pull request #4180 from yuwenma/fix-4124
[Fix 4124]  Skip local resource until all transformations have completed.
2021-09-24 08:05:19 -07:00
Jeff Regan
22b735885a Merge pull request #4190 from natasha41575/MigrateIndexPathIdAnnotations
Migrate index path id annotations
2021-09-24 08:00:15 -07:00
Kubernetes Prow Robot
b7c5058e37 Merge pull request #4177 from sylr/helm-v3.6
Upgrade Helm v3 to v3.6.3
2021-09-24 07:45:24 -07:00
Kubernetes Prow Robot
baff5f4359 Merge pull request #4187 from monopole/deanchorCall
Do YAML anchor expansion shortly after reading YAML.
2021-09-22 13:14:33 -07:00
monopole
dce4ea5846 Add AnchorsAweigh option to ByteReader to toggle YAML alias/anchor expansion 2021-09-22 12:44:08 -07:00
Kubernetes Prow Robot
c47fc48607 Merge pull request #4193 from phanimarupaka/AddressBareSeqNodeComments
Clean up bare sequence node wrapping
2021-09-20 15:46:23 -07:00
Phani Teja Marupaka
1d9b6cbe57 Clean up bare sequence node wrapping 2021-09-20 15:35:40 -07:00
Kubernetes Prow Robot
1cb93123fc Merge pull request #4189 from phanimarupaka/SkipNonKRMResources
Handle parsing of bare sequence yaml nodes
2021-09-20 11:40:24 -07:00
Kubernetes Prow Robot
c6cb42ec27 Merge pull request #4185 from KnVerey/standardize_owners_files
Use standard Kubernetes project roles for ownership
2021-09-20 10:44:10 -07:00
Natasha Sarkar
67a5f6d68f support krm spec v1 and legacy path, index, and id annotations 2021-09-17 17:10:10 -07:00
Phani Teja Marupaka
e997cc5486 Handle parsing of bare sequence yaml nodes 2021-09-17 14:19:27 -07:00
Kubernetes Prow Robot
53577a5190 Merge pull request #4176 from m-Bilal/fix-4123
Fixes 4123; Length check on originalFields of kustomizationFile to prevent panic
2021-09-16 13:15:27 -07:00
Kubernetes Prow Robot
c1ae234a64 Merge pull request #4163 from natasha41575/multipleGvksInOpenApi
support multiple gvks in custom openapi schema
2021-09-16 12:59:26 -07:00
Natasha Sarkar
02cb395ec2 support multiple gvks in custom openapi schema 2021-09-16 12:43:18 -07:00
Kubernetes Prow Robot
65e7529ca0 Merge pull request #4097 from natasha41575/deprecateFn
deprecate fn wrap, xargs, sink, source commands
2021-09-16 12:25:27 -07:00
Natasha Sarkar
f70743b267 deprecate some fn commands 2021-09-16 11:48:02 -07:00
Yuwen Ma
f4382738ab [fix 4124] Skip local resource until all transformations have completed.
Resources annotated as "local-config" are expected to be ignored. This skip local resource happens in "accumulateResources" which happens before any transformation operations.
However, the local resource may be needed in transformations.
Thus, this change removes the "drop local resource" logic from accumulateResources and removes these local resource after all transformation operations and var operations are done.

Note:
None of the existing ResMap functions can drop the resource slice easily: "Clear" will ruin the resource order, "AppendAll" only adds non-existing resource, "AbsorbAll" only add or modify but not delete.
Thus, we introduce a new func "Intersection" for resourceAccumulator that specificaly removes the resource by ID and keep the original order.
2021-09-16 11:15:05 -07:00
Yuwen Ma
a100dca303 [Fix 4124] Add unittest with the given example. 2021-09-16 11:14:07 -07:00
Jeff Regan
50414208d1 Merge pull request #4186 from monopole/cleanupTest
Clean up factor_test before adding DeAnchor call
2021-09-15 17:47:19 -07:00
monopole
e17a007719 Clean up factor_test before adding DeAnchor call 2021-09-15 17:26:50 -07:00
Katrina Verey
dd3c5f5c0a Use standard Kubernetes project roles for ownership 2021-09-15 16:04:58 -07:00
Sylvain Rabot
fb3f560e0c Upgrade Helm v3 to v3.6.3
Helm has started to build darwin/arm64 from v3.6.0.

Signed-off-by: Sylvain Rabot <sylvain@abstraction.fr>
2021-09-12 18:18:41 +02:00
m-Bilal
12c177a365 fixes 4123; added length check on originalFields of kustomizationFile to prevent panic when kustomization file began with a comment(or a blank line) followed by a document separator 2021-09-12 17:35:23 +05:30
John Howard
402f6ca72b Precompute IsNamespaceScoped to avoid expensive schema reads (#4152)
* Precompute IsNamespaceScoped to avoid expensive schema reads

See https://github.com/GoogleContainerTools/kpt/issues/2469

For the `gcr.io/kpt-fn/set-namespace:v0.1` function, over 50% of CPU
time is spent on IsNamespaceScoped. Instead of unmarshalling 100k lines
of JSON to determine this, instead just precompute it. We can ensure
this never is inaccurate as the test verifies the precomputed result is
up to date.

In real world kpt pipelines this cuts execution of set-namespace (and
similar functions, just an example of a trivial function) from 2.0s to
1.0s. Because these functions are run in long pipelines over many
resources, this adds up a lot.

* Add documentation
2021-09-09 10:08:11 -07:00
Kubernetes Prow Robot
2b8a39373e Merge pull request #4172 from phanimarupaka/CopyReferenceNodesBeforeComparing
Copy reference nodes before copying comments and syncing order
2021-09-08 11:29:56 -07:00
Phani Teja Marupaka
17f18604e4 Copy reference nodes before copying comments and syncing order 2021-09-07 16:58:06 -07:00
Kubernetes Prow Robot
99e404cb61 Merge pull request #4169 from invidian/fix-typo
api/krusty: fix typo fileystem -> filesystem
2021-09-07 11:41:17 -07:00
Kubernetes Prow Robot
d4e3b4f832 Merge pull request #4171 from justinsb/cache_orgid
Cache the OrgId for nameReferenceTransformer
2021-09-07 11:17:16 -07:00
Justin SB
6552b90657 Cache the OrgId for nameReferenceTransformer
Because this is in an inner loop and is fairly memory-allocation
expensive even on a single allocation, it comes up top-of-the-list in
memory allocation pprof profiles, for example with the coredns
ClusterAddon.

Add simple caching.
2021-09-07 14:06:38 -04:00
Mateusz Gozdek
bf57d698b1 api/krusty: fix typo fileystem -> filesystem
Part of https://github.com/kubernetes/kubernetes/pull/104747.

Signed-off-by: Mateusz Gozdek <mgozdekof@gmail.com>
2021-09-05 11:33:10 +02:00
Kubernetes Prow Robot
4d002af735 Merge pull request #4165 from natasha41575/nameRefAfterKindChange
update name references after kind change
2021-09-03 12:56:52 -07:00
Natasha Sarkar
2bfc7cc1b0 throw error instead of panic when replacements source.fieldPath doesn't exist (#4166)
* test for missing source.fieldPath in replacements

* throw error instead of panic when replacements source.fieldPath doesn't exist
2021-09-03 11:10:53 -07:00
Natasha Sarkar
0244f0919e update name references after kind change 2021-09-02 12:10:01 -07:00
Natasha Sarkar
f122fb12f3 Merge pull request #4158 from kubernetes-sigs/revert-4157-fix/github-rate-limiter-output
Revert "Return a meaningful message if we hit the rate-limiter of GitHub"
2021-08-31 13:29:11 -07:00
Natasha Sarkar
7d0b7e2113 Revert "Return a meaningful message if we hit the rate-limiter of GitHub" 2021-08-31 13:13:36 -07:00
Kubernetes Prow Robot
c3a67cfdca Merge pull request #4157 from osherdp/fix/github-rate-limiter-output
Return a meaningful message if we hit the rate-limiter of GitHub
2021-08-31 12:42:22 -07:00
Osher De Paz
4315e982be return a meaningful message if we hit the rate-limiter of GitHub 2021-08-31 16:19:39 +03:00
Kubernetes Prow Robot
634464353f Merge pull request #4128 from KnVerey/mini_proposal_process
Introduce in-repo proposal process
2021-08-30 17:22:28 -07:00
Kubernetes Prow Robot
9c36004493 Merge pull request #4143 from KnVerey/release_updates
Release updates
2021-08-30 16:08:29 -07:00
Katrina Verey
1b1034442c Enable real release after dry run without manual cleanup 2021-08-30 15:09:40 -07:00
Katrina Verey
a89863c84c Update release instructions 2021-08-30 15:09:40 -07:00
Katrina Verey
f7340e0615 Proposal template feedback 2021-08-30 12:10:02 -07:00
Kubernetes Prow Robot
bf6b207cc9 Merge pull request #4141 from KnVerey/unpinEverything
Back to development mode; unpin the modules
2021-08-24 12:46:58 -07:00
Katrina Verey
f93b4877f7 Back to development mode; unpin the modules 2021-08-24 12:35:26 -07:00
Kubernetes Prow Robot
cd17338759 Merge pull request #4139 from KnVerey/pinToApi
Pin to api v0.9.0
2021-08-24 12:04:58 -07:00
Katrina Verey
c46867c3a7 Pin to api v0.9.0 2021-08-24 11:54:43 -07:00
Katrina Verey
3e7246690f Introduce in-repo proposal process 2021-08-19 17:00:20 -07:00
84 changed files with 2746 additions and 414 deletions

View File

@@ -1,6 +1,25 @@
[SIG-CLI]: https://github.com/kubernetes/community/tree/master/sig-cli
[Slack channel]: https://kubernetes.slack.com/messages/kustomize
[Mailing list]: https://groups.google.com/forum/#!forum/kubernetes-sig-cli
[OWNERS file spec]: https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md
[Kustomize OWNERS_ALIASES]: https://github.com/kubernetes-sigs/kustomize/blob/8049f7b1af52e8a7ec26faf6cf714f560d0043c5/OWNERS_ALIASES
[SIG-CLI Teams]: https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml
[Github permissions]: https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization#repository-access-for-each-permission-level
[Contributor License Agreement]: https://git.k8s.io/community/CLA.md
[Kubernetes Contributor Guide]: http://git.k8s.io/community/contributors/guide
[Contributor Cheat Sheet]: https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md
[CNCF Code of Conduct]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md
[Kubernetes Community Membership]: https://github.com/kubernetes/community/blob/master/community-membership.md
[Contribution Guide]: https://kubectl.docs.kubernetes.io/contributing/kustomize/
[MacOS Dev Guide]: https://kubectl.docs.kubernetes.io/contributing/kustomize/mac/
[Windows Dev Guide]: https://kubectl.docs.kubernetes.io/contributing/kustomize/windows/
# Contributing Guidelines
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt:
Welcome to Kubernetes. We are excited about the prospect of you joining our [community](https://github.com/kubernetes/community)! The Kubernetes community abides by the [CNCF Code of Conduct]. Here is an excerpt:
_As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._
@@ -8,13 +27,22 @@ _As contributors and maintainers of this project, and in the interest of fosteri
Dev guides:
- [Mac](docs/macDevGuide.md)
- [Contribution Guide]
- [MacOS Dev Guide]
- [Windows Dev Guide]
We have full documentation on how to get started contributing here:
General resources for contributors:
- [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md) - Common resources for existing developers
- [Contributor License Agreement] - Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests.
- [Kubernetes Contributor Guide] - Main contributor documentation.
- [Contributor Cheat Sheet] - Common resources for existing developers.
Here are some additional ideas to help you get started with Kustomize:
- Attend a Kustomize Bug Scrub. Check the [SIG-CLI] meetings list to find the next one.
- Help triage issues by confirming validity and applying the appropriate `kind` label (e.g. comment `/kind bug`).
- Pick up an issue to fix. Issues with the `help-wanted` label are a good place to start, but you can also look for any issue with the `triage/accepted` label and no assignee. Remember to `/assign` yourself to let others know you're working on it.
- Help confirm new issues labelled `kind/bug` by reproducing them with the latest release.
- Support Kustomize users by responding to questions on issues labelled `kind/support` or in the [Slack channel].
## Mentorship
@@ -22,19 +50,22 @@ We have full documentation on how to get started contributing here:
## Contributor Ladder
Kustomize generally follows the [Kubernetes Community Membership](https://github.com/kubernetes/community/blob/master/community-membership.md) contributor ladder. Roles are as follows:
Kustomize follows the [Kubernetes Community Membership] contributor ladder. Roles are as follows:
1. Contributor: Anyone who actively contributes code, issues or reviews to the project. There are no Kustomize-specific requirements for this status. All contributors must [sign the CLA](https://github.com/kubernetes/community/tree/master/contributors/guide#prerequisites).
1. Member/Reviewer: All Kubernetes-SIGs org members have LGTM rights on the Kustomize repo. There are no Kustomize-specific requirements. Kustomize does not currently have any formal reviewers, but the role will be created if there is interest.
1. Maintainer/Approver: Highly experienced active reviewer and contributor to Kustomize. Has both LTGM and approval rights on the Kustomize repo, as well as [Github "maintain" rights](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization#repository-access-for-each-permission-level).
1. Admin/Owner: Maintainer who sets technical direction and makes or approves design decisions for the project. Has LGTM and approval rights on the Kustomize repo as well as [Github "admin" rights](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization#repository-access-for-each-permission-level).
1. Contributor: Anyone who actively contributes code, issues or reviews to the project. All contributors must sign the [Contributor License Agreement].
1. Reviewer: Contributors with a history of review and authorship on Kustomize. Has LGTM rights on the Kustomize repo (as do all kubernetes-sigs org members). Active contributors are encouraged to join the reviewers list to be automatically pinged on PRs.
1. Approver: Highly experienced active reviewer and contributor to Kustomize. Has both LTGM and approval rights on the Kustomize repo, as well as "maintain" [Github permissions].
1. Owner: Approver who sets technical direction and makes or approves design decisions for the project. Has LGTM and approval rights on the Kustomize repo as well as "admin" [Github permissions].
The kyaml module within the Kustomize repo has additional owners following the same ladder.
Administrative notes:
- Maintainers and admins must be added to the appropriate list both [in the Kustomize repo](https://github.com/kubernetes-sigs/kustomize/blob/8049f7b1af52e8a7ec26faf6cf714f560d0043c5/OWNERS_ALIASES) and [in the community repo](https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml). If this isn't done, the individual in question will lack either PR approval rights (Kustomize list) or the appropriate Github repository permissions (community list).
- The spec for the OWNERS file is [in the community repo](https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md).
- The [OWNERS file spec] is a useful resources in making changes.
- Maintainers and admins must be added to the appropriate lists in both [Kustomize OWNERS_ALIASES] and [SIG-CLI Teams]. If this isn't done, the individual in question will lack either PR approval rights (Kustomize list) or the appropriate Github repository permissions (community list).
## Contact Information
- [Slack channel](https://kubernetes.slack.com/messages/sig-cli)
- [Mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-cli)
- [Slack channel]
- [Mailing list]

View File

@@ -12,6 +12,7 @@ MYGOBIN = $(shell go env GOPATH)/bin
endif
export PATH := $(MYGOBIN):$(PATH)
MODULES := '"cmd/config" "api/" "kustomize/" "kyaml/"'
LATEST_V4_RELEASE=v4.3.0
# Provide defaults for REPO_OWNER and REPO_NAME if not present.
# Typically these values would be provided by Prow.
@@ -31,7 +32,7 @@ verify-kustomize: \
lint-kustomize \
test-unit-kustomize-all \
test-examples-kustomize-against-HEAD \
test-examples-kustomize-against-4.1
test-examples-kustomize-against-v4-release
# The following target referenced by a file in
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
@@ -44,7 +45,7 @@ prow-presubmit-check: \
test-unit-cmd-all \
test-go-mod \
test-examples-kustomize-against-HEAD \
test-examples-kustomize-against-4.1
test-examples-kustomize-against-v4-release
.PHONY: verify-kustomize-e2e
verify-kustomize-e2e: test-examples-e2e-kustomize
@@ -278,8 +279,8 @@ test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
./hack/testExamplesAgainstKustomize.sh HEAD
.PHONY:
test-examples-kustomize-against-4.1: $(MYGOBIN)/mdrip
./hack/testExamplesAgainstKustomize.sh v4@v4.1.2
test-examples-kustomize-against-v4-release: $(MYGOBIN)/mdrip
./hack/testExamplesAgainstKustomize.sh v4@$(LATEST_V4_RELEASE)
# linux only.
# This is for testing an example plugin that
@@ -319,7 +320,7 @@ $(MYGOBIN)/helmV3:
( \
set -e; \
d=$(shell mktemp -d); cd $$d; \
tgzFile=helm-v3.5.3-$(GOOS)-$(GOARCH).tar.gz; \
tgzFile=helm-v3.6.3-$(GOOS)-$(GOARCH).tar.gz; \
wget https://get.helm.sh/$$tgzFile; \
tar -xvzf $$tgzFile; \
mv $(GOOS)-$(GOARCH)/helm $(MYGOBIN)/helmV3; \

6
OWNERS
View File

@@ -1,4 +1,6 @@
# See https://github.com/kubernetes/community/blob/master/community-membership.md
approvers:
- kustomize-admins
- kustomize-maintainers
- kustomize-approvers
reviewers:
- kustomize-reviewers

View File

@@ -1,16 +1,30 @@
# Keep *-admins and *-maintainers list in sync with corresponding lists in
# Keep *-owners and *-approvers lists in sync with *-admins and *-maintainers in
# https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml
aliases:
kustomize-admins:
kustomize-owners:
- knverey
- monopole
- pwittrock
kustomize-maintainers:
kustomize-approvers:
- justinsb
- mortent
- knverey
- monopole
- natasha41575
- phanimarupaka
- Shell32-Natsu
emeritus-maintainers:
- liujingfang1
- pwittrock
kustomize-reviewers:
- knverey
- monopole
- natasha41575
kyaml-approvers:
- mengqiy
- mortent
- phanimarupaka
kyaml-reviewers:
- mengqiy
- mortent
- phanimarupaka
emeritus-approvers:
- liujingfang1
- Shell32-Natsu

View File

@@ -56,6 +56,7 @@ func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
// sanity check.
return nil, err
}
f.NameFieldToUpdate.Gvk = f.Referrer.GetGvk()
if err := node.PipeE(fieldspec.Filter{
FieldSpec: f.NameFieldToUpdate,
SetValue: f.set,

View File

@@ -250,6 +250,7 @@ metadata:
name: dep
annotations:
config.kubernetes.io/index: '0'
internal.config.kubernetes.io/index: '0'
data:
slice:
- false
@@ -276,6 +277,7 @@ metadata:
name: dep
annotations:
config.kubernetes.io/index: '0'
internal.config.kubernetes.io/index: '0'
data:
1: str
: invalid map key: value='1', tag='` + yaml.NodeTagInt + `'`,

View File

@@ -131,10 +131,11 @@ func getReplacement(nodes []*yaml.RNode, r *types.Replacement) (*yaml.RNode, err
if err != nil {
return nil, err
}
if !rn.IsNilOrEmpty() {
return getRefinedValue(r.Source.Options, rn)
if rn.IsNilOrEmpty() {
return nil, fmt.Errorf("fieldPath `%s` is missing for replacement source %s", r.Source.FieldPath, r.Source)
}
return rn, nil
return getRefinedValue(r.Source.Options, rn)
}
func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode, error) {

View File

@@ -1556,6 +1556,38 @@ spec:
name: postgresdb
`,
},
"replacement source.fieldPath does not exist": {
input: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
data:
grpcPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ports-to
data:
grpcPort: 8081
`,
replacements: `replacements:
- source:
kind: ConfigMap
name: ports-from
fieldPath: data.httpPort
targets:
- select:
kind: ConfigMap
name: ports-to
fieldPaths:
- data.grpcPort
options:
create: true
`,
expectedErr: "fieldPath `data.httpPort` is missing for replacement source ~G_~V_ConfigMap|~X|ports-from:data.httpPort",
},
}
for tn, tc := range testCases {

View File

@@ -11,6 +11,6 @@ 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.11.1
sigs.k8s.io/kustomize/kyaml v0.12.0
sigs.k8s.io/yaml v1.2.0
)

View File

@@ -223,8 +223,8 @@ k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
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/kustomize/kyaml v0.11.1 h1:MWihd9syKG7VQnAzr/OpKb94FvH+cw96nEV1u3it7pI=
sigs.k8s.io/kustomize/kyaml v0.11.1/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E=
sigs.k8s.io/kustomize/kyaml v0.12.0 h1:k08l8SLwnKa/eXXB5GW2/OnEc/4gJF90VDFebsOwqw4=
sigs.k8s.io/kustomize/kyaml v0.12.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -11,6 +11,7 @@ import (
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
"sigs.k8s.io/kustomize/api/resmap"
"sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/kyaml/resid"
)
type nameReferenceTransformer struct {
@@ -109,11 +110,18 @@ func debug(fMap filterMap) {
// 'spec/scaleTargetRef/name' field. Return a filter that can do that.
func (t *nameReferenceTransformer) determineFilters(
resources []*resource.Resource) (fMap filterMap) {
// We cache the resource OrgId values because they don't change and otherwise are very visible in a memory pprof
resourceOrgIds := make([]resid.ResId, len(resources))
for i, resource := range resources {
resourceOrgIds[i] = resource.OrgId()
}
fMap = make(filterMap)
for _, backReference := range t.backRefs {
for _, referrerSpec := range backReference.Referrers {
for _, res := range resources {
if res.OrgId().IsSelected(&referrerSpec.Gvk) {
for i, res := range resources {
if resourceOrgIds[i].IsSelected(&referrerSpec.Gvk) {
// If this is true, the res might be a referrer, and if
// so, the name reference it holds might need an update.
if resHasField(res, referrerSpec.Path) {

View File

@@ -168,3 +168,23 @@ func (ra *ResAccumulator) FixBackReferences() (err error) {
return ra.Transform(
newNameReferenceTransformer(ra.tConfig.NameReference))
}
// Intersection drops the resources which "other" does not have.
func (ra *ResAccumulator) Intersection(other resmap.ResMap) error {
for _, curId := range ra.resMap.AllIds() {
toDelete := true
for _, otherId := range other.AllIds() {
if otherId == curId {
toDelete = false
break
}
}
if toDelete {
err := ra.resMap.Remove(curId)
if err != nil {
return err
}
}
}
return nil
}

View File

@@ -10,6 +10,7 @@ import (
"strings"
"github.com/pkg/errors"
"sigs.k8s.io/kustomize/api/builtins"
"sigs.k8s.io/kustomize/api/ifc"
"sigs.k8s.io/kustomize/api/internal/accumulator"
@@ -218,9 +219,26 @@ func (kt *KustTarget) accumulateTarget(ra *accumulator.ResAccumulator, origin *r
return nil, errors.Wrapf(
err, "merging vars %v", kt.kustomization.Vars)
}
err = kt.IgnoreLocal(ra)
if err != nil {
return nil, err
}
return ra, nil
}
// IgnoreLocal drops the local resource by checking the annotation "config.kubernetes.io/local-config".
func (kt *KustTarget) IgnoreLocal(ra *accumulator.ResAccumulator) error {
rf := kt.rFactory.RF()
if rf.IncludeLocalConfigs {
return nil
}
remainRes, err := rf.DropLocalNodes(ra.ResMap().ToRNodeSlice())
if err != nil {
return err
}
return ra.Intersection(kt.rFactory.FromResourceSlice(remainRes))
}
func (kt *KustTarget) runGenerators(
ra *accumulator.ResAccumulator) error {
var generators []resmap.Generator

View File

@@ -685,3 +685,167 @@ spec:
name: new-name
`)
}
func TestNameReferenceAfterJsonPatch(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteF("resources.yaml", `
apiVersion: v1
data:
bar: bar
kind: ConfigMap
metadata:
name: cm
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: foo
spec:
selector:
matchLabels:
foo: foo
template:
metadata:
labels:
foo: foo
spec:
containers:
- name: foo
image: example
volumeMounts:
- mountPath: /path
name: myvol
volumes:
- configMap:
name: cm
name: myvol
`)
th.WriteK(".", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: foo-
resources:
- resources.yaml
patches:
- target:
group: apps
version: v1
name: foo
patch: |
- op: replace
path: /kind
value: Deployment
`)
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `apiVersion: v1
data:
bar: bar
kind: ConfigMap
metadata:
name: foo-cm
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo-foo
spec:
selector:
matchLabels:
foo: foo
template:
metadata:
labels:
foo: foo
spec:
containers:
- image: example
name: foo
volumeMounts:
- mountPath: /path
name: myvol
volumes:
- configMap:
name: foo-cm
name: myvol
`)
}
func TestNameReferenceAfterJsonPatchConfigMapGenerator(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteF("statefulset.yaml", `
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: foo
spec:
selector:
matchLabels:
foo: foo
template:
metadata:
labels:
foo: foo
spec:
containers:
- name: foo
image: example
volumeMounts:
- mountPath: /path
name: myvol
volumes:
- configMap:
name: cm
name: myvol
`)
th.WriteK(".", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- statefulset.yaml
patches:
- target:
group: apps
version: v1
name: foo
patch: |
- op: replace
path: /kind
value: Deployment
configMapGenerator:
- name: cm
literals:
- bar=bar
`)
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
spec:
selector:
matchLabels:
foo: foo
template:
metadata:
labels:
foo: foo
spec:
containers:
- image: example
name: foo
volumeMounts:
- mountPath: /path
name: myvol
volumes:
- configMap:
name: cm-8hm8224gfd
name: myvol
---
apiVersion: v1
data:
bar: bar
kind: ConfigMap
metadata:
name: cm-8hm8224gfd
`)
}

View File

@@ -26,7 +26,7 @@ import (
// used instead of performing an exec to a kustomize CLI subprocess.
// To use, load a filesystem with kustomization files (any
// number of overlays and bases), then make a Kustomizer
// injected with the given fileystem, then call Run.
// injected with the given filesystem, then call Run.
type Kustomizer struct {
options *Options
depProvider *provider.DepProvider

View File

@@ -0,0 +1,70 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package krusty_test
import (
"testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)
// This test checks that if a resource is annotated as "local-config", this resource won't be ignored until
// all transformations have completed. This makes sure the local resource can be used as a transformation input.
// See https://github.com/kubernetes-sigs/kustomize/issues/4124 for details.
func TestSKipLocalConfigAfterTransform(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- pod.yaml
- deployment.yaml
transformers:
- replacement.yaml
`)
th.WriteF("pod.yaml", `apiVersion: v1
kind: Pod
metadata:
name: buildup
annotations:
config.kubernetes.io/local-config: "true"
spec:
containers:
- name: app
image: nginx
`)
th.WriteF("deployment.yaml", `apiVersion: apps/v1
kind: Deployment
metadata:
name: buildup
`)
th.WriteF("replacement.yaml", `
apiVersion: builtin
kind: ReplacementTransformer
metadata:
name: buildup
replacements:
- source:
kind: Pod
fieldPath: spec
targets:
- select:
kind: Deployment
fieldPaths:
- spec.template.spec
options:
create: true
`)
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `apiVersion: apps/v1
kind: Deployment
metadata:
name: buildup
spec:
template:
spec:
containers:
- image: nginx
name: app
`)
}

View File

@@ -42,6 +42,26 @@ spec:
`)
}
func writeOtherCustomResource(th kusttest_test.Harness, filepath string) {
th.WriteF(filepath, `
apiVersion: v1alpha1
kind: MyCRD
metadata:
name: service
spec:
template:
spec:
containers:
- name: server
image: server
command: example
ports:
- name: grpc
protocol: TCP
containerPort: 8080
`)
}
func writeTestComponentWithCustomSchema(th kusttest_test.Harness) {
writeTestSchema(th, "comp/")
openapi.ResetOpenAPI()
@@ -74,6 +94,32 @@ patchesStrategicMerge:
image: nginx
`
const customSchemaPatchMultipleGvks = `
patchesStrategicMerge:
- |-
apiVersion: example.com/v1alpha1
kind: MyCRD
metadata:
name: service
spec:
template:
spec:
containers:
- name: server
image: nginx
- |-
apiVersion: v1alpha1
kind: MyCRD
metadata:
name: service
spec:
template:
spec:
containers:
- name: server
image: nginx
`
const patchedCustomResource = `
apiVersion: example.com/v1alpha1
kind: MyCRD
@@ -108,6 +154,54 @@ openapi:
th.AssertActualEqualsExpected(m, patchedCustomResource)
}
func TestCustomOpenApiFieldWithTwoGvks(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `
resources:
- mycrd.yaml
- myothercrd.yaml
openapi:
path: mycrd_schema.json
`+customSchemaPatchMultipleGvks)
writeCustomResource(th, "mycrd.yaml")
writeOtherCustomResource(th, "myothercrd.yaml")
writeTestSchema(th, "./")
openapi.ResetOpenAPI()
m := th.Run(".", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `apiVersion: example.com/v1alpha1
kind: MyCRD
metadata:
name: service
spec:
template:
spec:
containers:
- command: example
image: nginx
name: server
ports:
- containerPort: 8080
name: grpc
protocol: TCP
---
apiVersion: v1alpha1
kind: MyCRD
metadata:
name: service
spec:
template:
spec:
containers:
- command: example
image: nginx
name: server
ports:
- containerPort: 8080
name: grpc
protocol: TCP
`)
}
func TestCustomOpenApiFieldYaml(t *testing.T) {
th := kusttest_test.MakeHarness(t)
th.WriteK(".", `

View File

@@ -34,6 +34,11 @@
"group": "example.com",
"kind": "MyCRD",
"version": "v1alpha1"
},
{
"group": "",
"kind": "MyCRD",
"version": "v1alpha1"
}
]
},

View File

@@ -22,6 +22,9 @@ definitions:
- group: example.com
kind: MyCRD
version: v1alpha1
- group: ""
kind: MyCRD
version: v1alpha1
io.k8s.api.core.v1.PodTemplateSpec:
properties:
metadata:

View File

@@ -124,14 +124,34 @@ func (rf *Factory) SliceFromBytes(in []byte) ([]*Resource, error) {
return rf.resourcesFromRNodes(nodes), nil
}
// DropLocalNodes removes the local nodes by default. Local nodes are detected via the annotation `config.kubernetes.io/local-config: "true"`
func (rf *Factory) DropLocalNodes(nodes []*yaml.RNode) ([]*Resource, error) {
var result []*yaml.RNode
for _, node := range nodes {
if node.IsNilOrEmpty() {
continue
}
md, err := node.GetValidatedMetadata()
if err != nil {
return nil, err
}
if rf.IncludeLocalConfigs {
result = append(result, node)
continue
}
localConfig, exist := md.ObjectMeta.Annotations[konfig.IgnoredByKustomizeAnnotation]
if !exist || localConfig == "false" {
result = append(result, node)
}
}
return rf.resourcesFromRNodes(result), nil
}
// ResourcesFromRNodes converts RNodes to Resources.
func (rf *Factory) ResourcesFromRNodes(
nodes []*yaml.RNode) (result []*Resource, err error) {
nodes, err = rf.dropBadNodes(nodes)
if err != nil {
return nil, err
}
return rf.resourcesFromRNodes(nodes), nil
return rf.DropLocalNodes(nodes)
}
// resourcesFromRNode assumes all nodes are good.
@@ -143,7 +163,7 @@ func (rf *Factory) resourcesFromRNodes(
return
}
func (rf *Factory) RNodesFromBytes(b []byte) (result []*yaml.RNode, err error) {
func (rf *Factory) RNodesFromBytes(b []byte) ([]*yaml.RNode, error) {
nodes, err := kio.FromBytes(b)
if err != nil {
return nil, err
@@ -152,9 +172,17 @@ func (rf *Factory) RNodesFromBytes(b []byte) (result []*yaml.RNode, err error) {
if err != nil {
return nil, err
}
return rf.inlineAnyEmbeddedLists(nodes)
}
// inlineAnyEmbeddedLists scans the RNode slice for nodes named FooList.
// Such nodes are expected to be lists of resources, each of type Foo.
// These lists are replaced in the result by their inlined resources.
func (rf *Factory) inlineAnyEmbeddedLists(
nodes []*yaml.RNode) (result []*yaml.RNode, err error) {
var n0 *yaml.RNode
for len(nodes) > 0 {
n0 := nodes[0]
nodes = nodes[1:]
n0, nodes = nodes[0], nodes[1:]
kind := n0.GetKind()
if !strings.HasSuffix(kind, "List") {
result = append(result, n0)
@@ -164,7 +192,7 @@ func (rf *Factory) RNodesFromBytes(b []byte) (result []*yaml.RNode, err error) {
var m map[string]interface{}
m, err = n0.Map()
if err != nil {
return nil, err
return nil, fmt.Errorf("trouble expanding list of %s; %w", kind, err)
}
items, ok := m["items"]
if !ok {
@@ -216,38 +244,20 @@ func (rf *Factory) convertObjectSliceToNodeSlice(
func (rf *Factory) dropBadNodes(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
var result []*yaml.RNode
for _, n := range nodes {
ignore, err := rf.shouldIgnore(n)
if err != nil {
if n.IsNilOrEmpty() {
continue
}
if _, err := n.GetValidatedMetadata(); err != nil {
return nil, err
}
if !ignore {
result = append(result, n)
if foundNil, path := n.HasNilEntryInList(); foundNil {
return nil, fmt.Errorf("empty item at %v in object %v", path, n)
}
result = append(result, n)
}
return result, nil
}
// shouldIgnore returns true if there's some reason to ignore the node.
func (rf *Factory) shouldIgnore(n *yaml.RNode) (bool, error) {
if n.IsNilOrEmpty() {
return true, nil
}
if !rf.IncludeLocalConfigs {
md, err := n.GetValidatedMetadata()
if err != nil {
return true, err
}
_, ignore := md.ObjectMeta.Annotations[konfig.IgnoredByKustomizeAnnotation]
if ignore {
return true, nil
}
}
if foundNil, path := n.HasNilEntryInList(); foundNil {
return true, fmt.Errorf("empty item at %v in object %v", path, n)
}
return false, nil
}
// SliceFromBytesWithNames unmarshals bytes into a Resource slice with specified original
// name.
func (rf *Factory) SliceFromBytesWithNames(names []string, in []byte) ([]*Resource, error) {

View File

@@ -5,7 +5,7 @@ package resource_test
import (
"fmt"
"reflect"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -13,9 +13,10 @@ import (
. "sigs.k8s.io/kustomize/api/resource"
"sigs.k8s.io/kustomize/api/types"
"sigs.k8s.io/kustomize/kyaml/filesys"
"sigs.k8s.io/kustomize/kyaml/kio"
)
func TestSliceFromBytes(t *testing.T) {
func TestRNodesFromBytes(t *testing.T) {
type testCase struct {
input string
expected []string
@@ -399,60 +400,11 @@ binaryData:
}
}
func TestSliceFromBytesMore(t *testing.T) {
testConfigMap :=
map[string]interface{}{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "winnie",
},
}
testDeploymentSpec := map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"hostAliases": []interface{}{
map[string]interface{}{
"hostnames": []interface{}{
"a.example.com",
},
"ip": "8.8.8.8",
},
},
},
},
}
testDeploymentA := map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deployment-a",
},
"spec": testDeploymentSpec,
}
testDeploymentB := map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deployment-b",
},
"spec": testDeploymentSpec,
}
testDeploymentList :=
map[string]interface{}{
"apiVersion": "v1",
"kind": "DeploymentList",
"items": []interface{}{
testDeploymentA,
testDeploymentB,
},
}
func TestMoreRNodesFromBytes(t *testing.T) {
type expected struct {
out []map[string]interface{}
out []string
isErr bool
}
testCases := map[string]struct {
input []byte
exp expected
@@ -465,16 +417,16 @@ func TestSliceFromBytesMore(t *testing.T) {
},
"noBytes": {
input: []byte{},
exp: expected{
out: []map[string]interface{}{},
},
exp: expected{},
},
"goodJson": {
input: []byte(`
{"apiVersion":"v1","kind":"ConfigMap","metadata":{"name":"winnie"}}
`),
exp: expected{
out: []map[string]interface{}{testConfigMap},
out: []string{
`{"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}`,
},
},
},
"goodYaml1": {
@@ -485,7 +437,12 @@ metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{testConfigMap},
out: []string{`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`},
},
},
"goodYaml2": {
@@ -501,26 +458,17 @@ metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{testConfigMap, testConfigMap},
},
},
"localConfigYaml": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie-skip
annotations:
# this annotation causes the Resource to be ignored by kustomize
config.kubernetes.io/local-config: ""
---
out: []string{`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{testConfigMap},
`, `
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`},
},
},
"garbageInOneOfTwoObjects": {
@@ -545,7 +493,7 @@ WOOOOOOOOOOOOOOOOOOOOOOOOT: woot
`),
exp: expected{
out: []map[string]interface{}{},
out: []string{},
},
},
"Missing .metadata.name in object": {
@@ -591,9 +539,18 @@ items:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{
testConfigMap,
testConfigMap},
out: []string{`
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`, `
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`,
},
},
},
"ConfigMapList": {
@@ -611,9 +568,9 @@ items:
name: winnie
`),
exp: expected{
out: []map[string]interface{}{
testConfigMap,
testConfigMap,
out: []string{
`{"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}`,
`{"apiVersion": "v1", "kind": "ConfigMap", "metadata": {"name": "winnie"}}`,
},
},
},
@@ -626,7 +583,7 @@ items:
kind: Deployment
metadata:
name: deployment-a
spec: &hostAliases
spec: &foo
template:
spec:
hostAliases:
@@ -638,23 +595,39 @@ items:
metadata:
name: deployment-b
spec:
<<: *hostAliases
*foo
`),
exp: expected{
// TODO(3271): This should work.
// https://github.com/kubernetes-sigs/kustomize/issues/3271
// json.Marshal(obj) fails on the 2nd list item.
// The value of the 1st list item's first spec field is
// map[string]interface{}
// The value of the 2nd list item's first spec field is
// map[interface{}]interface{}
// which causes a encoding/json.UnsupportedTypeError.
isErr: true,
out: []map[string]interface{}{testDeploymentList},
out: []string{
`{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "deployment-a"}, ` +
`"spec": {"template": {"spec": {"hostAliases": [{"hostnames": ["a.example.com"], "ip": "8.8.8.8"}]}}}}`,
`{"apiVersion": "apps/v1", "kind": "Deployment", "metadata": {"name": "deployment-b"}, ` +
`"spec": {"template": {"spec": {"hostAliases": [{"hostnames": ["a.example.com"], "ip": "8.8.8.8"}]}}}}`},
},
},
"simpleAnchor": {
input: []byte(`
apiVersion: v1
kind: ConfigMap
metadata:
name: wildcard
data:
color: &color-used blue
feeling: *color-used
`),
exp: expected{
out: []string{`
apiVersion: v1
kind: ConfigMap
metadata:
name: wildcard
data:
color: blue
feeling: blue
`},
},
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
@@ -666,15 +639,100 @@ items:
assert.False(t, tc.exp.isErr)
assert.Equal(t, len(tc.exp.out), len(rs))
for i := range rs {
rsMap, err := rs[i].Map()
actual, err := rs[i].String()
assert.NoError(t, err)
assert.Equal(
t, fmt.Sprintf("%v", tc.exp.out[i]), fmt.Sprintf("%v", rsMap))
m, _ := rs[i].Map()
if !reflect.DeepEqual(tc.exp.out[i], m) {
t.Fatalf("%s:\nexpected: %v\n actual: %v",
n, tc.exp.out[i], m)
}
t, strings.TrimSpace(tc.exp.out[i]), strings.TrimSpace(actual))
}
})
}
}
func TestDropLocalNodes(t *testing.T) {
testCases := map[string]struct {
input []byte
expected []byte
}{
"localConfigUnset": {
input: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
expected: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
},
"localConfigSet": {
input: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie-skip
annotations:
# this annotation causes the Resource to be ignored by kustomize
config.kubernetes.io/local-config: ""
`),
expected: nil,
},
"localConfigSetToTrue": {
input: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie-skip
annotations:
config.kubernetes.io/local-config: "true"
`),
expected: nil,
},
"localConfigSetToFalse": {
input: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
annotations:
config.kubernetes.io/local-config: "false"
`),
expected: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
annotations:
config.kubernetes.io/local-config: "false"
name: winnie
`),
},
"localConfigMultiInput": {
input: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
---
apiVersion: v1
kind: ConfigMap
metadata:
name: winnie-skip
annotations:
config.kubernetes.io/local-config: "true"
`),
expected: []byte(`apiVersion: v1
kind: ConfigMap
metadata:
name: winnie
`),
},
}
for n := range testCases {
tc := testCases[n]
t.Run(n, func(t *testing.T) {
nin, _ := kio.FromBytes(tc.input)
res, err := factory.DropLocalNodes(nin)
assert.NoError(t, err)
if tc.expected == nil {
assert.Equal(t, 0, len(res))
} else {
actual, _ := res[0].AsYAML()
assert.Equal(t, tc.expected, actual)
}
})
}

View File

@@ -41,6 +41,9 @@ var BuildAnnotations = []string{
kioutil.PathAnnotation,
kioutil.IndexAnnotation,
kioutil.SeqIndentAnnotation,
kioutil.LegacyPathAnnotation,
kioutil.LegacyIndexAnnotation,
}
func (r *Resource) ResetRNode(incoming *Resource) {

6
cmd/config/OWNERS Normal file
View File

@@ -0,0 +1,6 @@
# See https://github.com/kubernetes/community/blob/master/community-membership.md
approvers:
- kyaml-approvers
reviewers:
- kyaml-reviewers

View File

@@ -16,5 +16,5 @@ 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.11.1
sigs.k8s.io/kustomize/kyaml v0.12.0
)

View File

@@ -245,8 +245,8 @@ k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
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/kustomize/kyaml v0.11.1 h1:MWihd9syKG7VQnAzr/OpKb94FvH+cw96nEV1u3it7pI=
sigs.k8s.io/kustomize/kyaml v0.11.1/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E=
sigs.k8s.io/kustomize/kyaml v0.12.0 h1:k08l8SLwnKa/eXXB5GW2/OnEc/4gJF90VDFebsOwqw4=
sigs.k8s.io/kustomize/kyaml v0.12.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -164,6 +164,8 @@ metadata:
a: 'b'
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
---
@@ -175,6 +177,8 @@ metadata:
a: 'b'
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
@@ -191,6 +195,8 @@ metadata:
a: 'b'
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f2.yaml'
namespace: bar
spec:
replicas: 3
@@ -206,6 +212,8 @@ metadata:
a: 'b'
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f2.yaml'
namespace: foo
spec:
replicas: 3
@@ -222,6 +230,8 @@ metadata:
c: 'd'
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
---
@@ -234,6 +244,8 @@ metadata:
c: 'd'
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
@@ -251,6 +263,8 @@ metadata:
c: 'd'
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f2.yaml'
namespace: bar
spec:
replicas: 3
@@ -267,6 +281,8 @@ metadata:
c: 'd'
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f2.yaml'
namespace: foo
spec:
replicas: 3
@@ -281,6 +297,8 @@ metadata:
app: nginx2
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
---
@@ -292,6 +310,8 @@ metadata:
a: 'b'
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
@@ -307,6 +327,8 @@ metadata:
config.kubernetes.io/local-config: "true"
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f2.yaml'
namespace: bar
spec:
replicas: 3
@@ -321,6 +343,8 @@ metadata:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f2.yaml'
namespace: foo
spec:
replicas: 3
@@ -335,6 +359,8 @@ metadata:
app: nginx2
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
---
@@ -345,6 +371,8 @@ metadata:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
@@ -361,6 +389,8 @@ metadata:
a: 'b'
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f2.yaml'
namespace: bar
spec:
replicas: 3
@@ -375,6 +405,8 @@ metadata:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f2.yaml'
namespace: foo
spec:
replicas: 3
@@ -389,6 +421,8 @@ metadata:
app: nginx2
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
---
@@ -399,6 +433,8 @@ metadata:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
@@ -414,6 +450,8 @@ metadata:
config.kubernetes.io/local-config: "true"
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f2.yaml'
namespace: bar
spec:
replicas: 3
@@ -429,6 +467,8 @@ metadata:
a: 'b'
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f2.yaml'
namespace: foo
spec:
replicas: 3
@@ -443,6 +483,8 @@ metadata:
app: nginx2
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
---
@@ -453,6 +495,8 @@ metadata:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
@@ -469,6 +513,8 @@ metadata:
a: 'b'
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f2.yaml'
namespace: bar
spec:
replicas: 3
@@ -483,6 +529,8 @@ metadata:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f2.yaml'
namespace: foo
spec:
replicas: 3

View File

@@ -17,6 +17,7 @@ import (
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/filters"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
@@ -182,7 +183,7 @@ func (r *CatRunner) out(w io.Writer) ([]kio.Writer, error) {
// remove this annotation explicitly, the ByteWriter won't clear it by
// default because it doesn't set it
clear := []string{"config.kubernetes.io/path"}
clear := []string{kioutil.LegacyPathAnnotation, kioutil.PathAnnotation}
if r.KeepAnnotations {
clear = nil
}

View File

@@ -5,6 +5,7 @@ package commands
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
@@ -47,6 +48,7 @@ Environment Variables:
`,
RunE: r.runE,
PreRunE: r.preRunE,
SilenceUsage: true,
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
Args: cobra.MinimumNArgs(1),
@@ -78,6 +80,12 @@ func WrapCommand() *cobra.Command {
return GetWrapRunner().Command
}
func (r *WrapRunner) preRunE(_ *cobra.Command, _ []string) error {
_, err := fmt.Fprintln(os.Stderr, `Command "wrap" is deprecated, this will no longer be available in kustomize v5.
See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.`)
return err
}
func (r *WrapRunner) runE(c *cobra.Command, args []string) error {
if r.getEnv == nil {
r.getEnv = os.Getenv

View File

@@ -81,6 +81,8 @@ items:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/test_deployment.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'config/test_deployment.yaml'
spec:
replicas: 11
selector:
@@ -112,6 +114,8 @@ items:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/test_service.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'config/test_service.yaml'
spec:
selector:
name: test
@@ -136,6 +140,8 @@ items:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/test_deployment.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'config/test_deployment.yaml'
spec:
replicas: 11
selector:
@@ -164,6 +170,8 @@ items:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/test_service.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'config/test_service.yaml'
spec:
selector:
name: test
@@ -186,6 +194,8 @@ items:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/mysql-deployment_deployment.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'config/mysql-deployment_deployment.yaml'
spec:
replicas: 3
template:
@@ -201,6 +211,8 @@ items:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/nosetters-deployment_deployment.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'config/nosetters-deployment_deployment.yaml'
spec:
replicas: 4
template:
@@ -216,6 +228,8 @@ items:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/storage-deployment_deployment.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'config/storage-deployment_deployment.yaml'
spec:
replicas: 4
template:
@@ -233,6 +247,8 @@ items:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/test_deployment.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'config/test_deployment.yaml'
spec:
replicas: 11
selector:
@@ -264,6 +280,8 @@ items:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'config/test_service.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'config/test_service.yaml'
spec:
selector:
name: test

View File

@@ -60,6 +60,7 @@ $ kyaml cat pkg/ --function-config config.yaml --wrap-kind ResourceList | kyaml
`,
RunE: r.runE,
PreRunE: r.preRunE,
SilenceUsage: true,
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
Args: cobra.MinimumNArgs(1),
@@ -84,6 +85,12 @@ func XArgsCommand() *cobra.Command {
return GetXArgsRunner().Command
}
func (r *XArgsRunner) preRunE(_ *cobra.Command, _ []string) error {
_, err := fmt.Fprintln(os.Stderr, `Command "xargs" is deprecated, this will no longer be available in kustomize v5.
See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.`)
return err
}
func (r *XArgsRunner) runE(c *cobra.Command, _ []string) error {
if len(r.Args) == 0 {
r.Args = os.Args

View File

@@ -79,6 +79,8 @@ metadata:
app: nginx2
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
---
@@ -89,6 +91,8 @@ metadata:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
@@ -146,6 +150,7 @@ metadata:
annotations:
app: nginx2
config.kubernetes.io/index: '0'
internal.config.kubernetes.io/index: '0'
spec:
replicas: 1
---
@@ -155,6 +160,7 @@ metadata:
annotations:
app: nginx
config.kubernetes.io/index: '1'
internal.config.kubernetes.io/index: '1'
spec:
selector:
app: nginx
@@ -295,6 +301,8 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'deployment.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'deployment.yaml'
spec:
replicas: 3
template:
@@ -314,6 +322,8 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'deployment.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'deployment.yaml'
spec:
replicas: 4
template:
@@ -339,6 +349,8 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'deployment.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'deployment.yaml'
spec:
replicas: 3
template:
@@ -364,6 +376,8 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'deployment.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'deployment.yaml'
spec:
replicas: 4
template:

View File

@@ -4,6 +4,9 @@
package commands
import (
"fmt"
"os"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
"sigs.k8s.io/kustomize/cmd/config/runner"
@@ -20,6 +23,7 @@ func GetSinkRunner(name string) *SinkRunner {
Long: commands.SinkLong,
Example: commands.SinkExamples,
RunE: r.runE,
PreRunE: r.preRunE,
Args: cobra.MaximumNArgs(1),
}
runner.FixDocs(name, c)
@@ -36,6 +40,12 @@ type SinkRunner struct {
Command *cobra.Command
}
func (r *SinkRunner) preRunE(c *cobra.Command, args []string) error {
_, err := fmt.Fprintln(os.Stderr, `Command "sink" is deprecated, this will no longer be available in kustomize v5.
See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.`)
return err
}
func (r *SinkRunner) runE(c *cobra.Command, args []string) error {
var outputs []kio.Writer
if len(args) == 1 {
@@ -43,7 +53,7 @@ func (r *SinkRunner) runE(c *cobra.Command, args []string) error {
} else {
outputs = []kio.Writer{&kio.ByteWriter{
Writer: c.OutOrStdout(),
ClearAnnotations: []string{kioutil.PathAnnotation}},
ClearAnnotations: []string{kioutil.PathAnnotation, kioutil.LegacyPathAnnotation}},
}
}

View File

@@ -5,6 +5,7 @@ package commands
import (
"fmt"
"os"
"github.com/spf13/cobra"
"sigs.k8s.io/kustomize/cmd/config/internal/generateddocs/commands"
@@ -22,6 +23,7 @@ func GetSourceRunner(name string) *SourceRunner {
Long: commands.SourceLong,
Example: commands.SourceExamples,
RunE: r.runE,
PreRunE: r.preRunE,
}
runner.FixDocs(name, c)
c.Flags().StringVar(&r.WrapKind, "wrap-kind", kio.ResourceListKind,
@@ -47,6 +49,12 @@ type SourceRunner struct {
Command *cobra.Command
}
func (r *SourceRunner) preRunE(c *cobra.Command, args []string) error {
_, err := fmt.Fprintln(os.Stderr, `Command "source" is deprecated, this will no longer be available in kustomize v5.
See discussion in https://github.com/kubernetes-sigs/kustomize/issues/3953.`)
return err
}
func (r *SourceRunner) runE(c *cobra.Command, args []string) error {
// if there is a function-config specified, emit it
var functionConfig *yaml.RNode

View File

@@ -93,6 +93,8 @@ items:
app: nginx2
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
replicas: 1
- kind: Service
@@ -102,6 +104,8 @@ items:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f1.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f1.yaml'
spec:
selector:
app: nginx
@@ -116,6 +120,8 @@ items:
config.kubernetes.io/local-config: "true"
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'f2.yaml'
spec:
replicas: 3
- apiVersion: apps/v1
@@ -128,6 +134,8 @@ items:
app: nginx
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'f2.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'f2.yaml'
spec:
replicas: 3
`, b.String()) {
@@ -194,8 +202,8 @@ func TestSourceCommandJSON(t *testing.T) {
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- {"kind": "Deployment", "metadata": {"labels": {"app": "nginx2"}, "name": "foo", "annotations": {"app": "nginx2", config.kubernetes.io/index: '0', config.kubernetes.io/path: 'f1.json'}}, "spec": {"replicas": 1}}
- {"apiVersion": "v1", "kind": "Abstraction", "metadata": {"name": "foo", "annotations": {"config.kubernetes.io/function": "container:\n image: gcr.io/example/reconciler:v1\n", "config.kubernetes.io/local-config": "true", config.kubernetes.io/index: '0', config.kubernetes.io/path: 'f2.json'}}, "spec": {"replicas": 3}}
- {"kind": "Deployment", "metadata": {"labels": {"app": "nginx2"}, "name": "foo", "annotations": {"app": "nginx2", config.kubernetes.io/index: '0', config.kubernetes.io/path: 'f1.json', internal.config.kubernetes.io/index: '0', internal.config.kubernetes.io/path: 'f1.json'}}, "spec": {"replicas": 1}}
- {"apiVersion": "v1", "kind": "Abstraction", "metadata": {"name": "foo", "annotations": {"config.kubernetes.io/function": "container:\n image: gcr.io/example/reconciler:v1\n", "config.kubernetes.io/local-config": "true", config.kubernetes.io/index: '0', config.kubernetes.io/path: 'f2.json', internal.config.kubernetes.io/index: '0', internal.config.kubernetes.io/path: 'f2.json'}}, "spec": {"replicas": 3}}
`, b.String()) {
return
}
@@ -249,6 +257,7 @@ items:
annotations:
app: nginx2
config.kubernetes.io/index: '0'
internal.config.kubernetes.io/index: '0'
spec:
replicas: 1
- kind: Service
@@ -257,6 +266,7 @@ items:
annotations:
app: nginx
config.kubernetes.io/index: '1'
internal.config.kubernetes.io/index: '1'
spec:
selector:
app: nginx
@@ -302,7 +312,7 @@ func TestSourceCommandJSON_Stdin(t *testing.T) {
if !assert.Equal(t, `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- {"kind": "Deployment", "metadata": {"labels": {"app": "nginx2"}, "name": "foo", "annotations": {"app": "nginx2", config.kubernetes.io/index: '0'}}, "spec": {"replicas": 1}}
- {"kind": "Deployment", "metadata": {"labels": {"app": "nginx2"}, "name": "foo", "annotations": {"app": "nginx2", config.kubernetes.io/index: '0', internal.config.kubernetes.io/index: '0'}}, "spec": {"replicas": 1}}
`, out.String()) {
return
}

View File

@@ -278,12 +278,13 @@ func (gr *Runner) CheckoutReleaseBranch(
return nil
}
gr.comment("creating branch")
// The branch doesn't exist. Create it.
out, err := gr.run(noHarmDone, "checkout", "-b", branch)
// The branch doesn't exist remotely. Create or reset it locally.
out, err := gr.run(noHarmDone, "checkout", "-B", branch)
if err != nil {
return err
}
if !strings.Contains(out, "Switched to a new branch ") {
// Expected strings: "Switched to a new branch" or "Switched to and reset branch"
if !strings.Contains(out, "Switched to") {
return fmt.Errorf("unexpected branch creation output: %q", out)
}
return nil

View File

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

View File

@@ -228,8 +228,8 @@ k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
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/kustomize/kyaml v0.11.1 h1:MWihd9syKG7VQnAzr/OpKb94FvH+cw96nEV1u3it7pI=
sigs.k8s.io/kustomize/kyaml v0.11.1/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E=
sigs.k8s.io/kustomize/kyaml v0.12.0 h1:k08l8SLwnKa/eXXB5GW2/OnEc/4gJF90VDFebsOwqw4=
sigs.k8s.io/kustomize/kyaml v0.12.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

View File

@@ -212,7 +212,7 @@ func (mf *kustomizationFile) parseCommentedFields(content []byte) error {
if matched {
mf.originalFields = append(mf.originalFields, &commentedField{field: field, comment: squash(comments)})
comments = [][]byte{}
} else if len(comments) > 0 {
} else if len(comments) > 0 && len(mf.originalFields) > 0 {
mf.originalFields[len(mf.originalFields)-1].appendComment(squash(comments))
comments = [][]byte{}
}

View File

@@ -356,6 +356,53 @@ kind: Kustomization
}
}
func TestCommentsWithDocumentSeperatorAtBeginning(t *testing.T) {
kustomizationContentWithComments := []byte(`
# Some comments
# This is some comment we should preserve
# don't delete it
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: mynamespace
`)
expected := []byte(`
# Some comments
# This is some comment we should preserve
# don't delete it
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: mynamespace
`)
fSys := filesys.MakeFsInMemory()
testutils_test.WriteTestKustomizationWith(
fSys, kustomizationContentWithComments)
mf, err := NewKustomizationFile(fSys)
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
kustomization, err := mf.Read()
if err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
if err = mf.Write(kustomization); err != nil {
t.Fatalf("Unexpected Error: %v", err)
}
bytes, _ := fSys.ReadFile(mf.path)
if diff := cmp.Diff(expected, bytes); diff != "" {
t.Errorf("Mismatch (-expected, +actual):\n%s", diff)
}
}
func TestUnknownFieldInKustomization(t *testing.T) {
kContent := []byte(`
foo:

View File

@@ -8,9 +8,9 @@ require (
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
sigs.k8s.io/kustomize/api v0.8.11
sigs.k8s.io/kustomize/cmd/config v0.10.0
sigs.k8s.io/kustomize/kyaml v0.11.1
sigs.k8s.io/kustomize/api v0.9.0
sigs.k8s.io/kustomize/cmd/config v0.10.1
sigs.k8s.io/kustomize/kyaml v0.12.0
sigs.k8s.io/yaml v1.2.0
)

View File

@@ -253,10 +253,10 @@ k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
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/kustomize/cmd/config v0.10.0 h1:6XPkaOu9eFdvrcqHpfmYAKoBvE01JdvsTVvtDuAFM+8=
sigs.k8s.io/kustomize/cmd/config v0.10.0/go.mod h1:XS4NFKucjk0/+nPKJwSMoZYCjey2PB0ipNoZabXUFPU=
sigs.k8s.io/kustomize/kyaml v0.11.1 h1:MWihd9syKG7VQnAzr/OpKb94FvH+cw96nEV1u3it7pI=
sigs.k8s.io/kustomize/kyaml v0.11.1/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E=
sigs.k8s.io/kustomize/cmd/config v0.10.1 h1:eqpN9eUSn3XIfvPabit8lpIqUbWKS7f4lOB4D2cr5CQ=
sigs.k8s.io/kustomize/cmd/config v0.10.1/go.mod h1:9W5pDv3cgDfMjOXEga4yC9lUpkgAaecW+lZmHOMeX2I=
sigs.k8s.io/kustomize/kyaml v0.12.0 h1:k08l8SLwnKa/eXXB5GW2/OnEc/4gJF90VDFebsOwqw4=
sigs.k8s.io/kustomize/kyaml v0.12.0/go.mod h1:FTJxEZ86ScK184NpGSAQcfEqee0nul8oLCK30D47m4E=
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
kyaml/OWNERS Normal file
View File

@@ -0,0 +1,6 @@
# See https://github.com/kubernetes/community/blob/master/community-membership.md
approvers:
- kyaml-approvers
reviewers:
- kyaml-reviewers

View File

@@ -11,10 +11,12 @@ import (
// CopyComments recursively copies the comments on fields in from to fields in to
func CopyComments(from, to *yaml.RNode) error {
copy(from, to)
// from node should not be modified, it should be just used as a reference
fromCopy := from.Copy()
copyFieldComments(fromCopy, to)
// walk the fields copying comments
_, err := walk.Walker{
Sources: []*yaml.RNode{from, to},
Sources: []*yaml.RNode{fromCopy, to},
Visitor: &copier{},
VisitKeysAsScalars: true}.Walk()
return err
@@ -25,7 +27,7 @@ func CopyComments(from, to *yaml.RNode) error {
type copier struct{}
func (c *copier) VisitMap(s walk.Sources, _ *openapi.ResourceSchema) (*yaml.RNode, error) {
copy(s.Dest(), s.Origin())
copyFieldComments(s.Dest(), s.Origin())
return s.Dest(), nil
}
@@ -39,13 +41,13 @@ func (c *copier) VisitScalar(s walk.Sources, _ *openapi.ResourceSchema) (*yaml.R
to.Document().Style = yaml.DoubleQuotedStyle
}
copy(s.Dest(), to)
copyFieldComments(s.Dest(), to)
return s.Dest(), nil
}
func (c *copier) VisitList(s walk.Sources, _ *openapi.ResourceSchema, _ walk.ListKind) (
*yaml.RNode, error) {
copy(s.Dest(), s.Origin())
copyFieldComments(s.Dest(), s.Origin())
destItems := s.Dest().Content()
originItems := s.Origin().Content()
@@ -64,8 +66,8 @@ func (c *copier) VisitList(s walk.Sources, _ *openapi.ResourceSchema, _ walk.Lis
return s.Dest(), nil
}
// copy copies the comment from one field to another
func copy(from, to *yaml.RNode) {
// copyFieldComments copies the comment from one field to another
func copyFieldComments(from, to *yaml.RNode) {
if from == nil || to == nil {
return
}

View File

@@ -351,6 +351,30 @@ apiVersion: v1
kind: ConfigMap
data:
somekey: "012345678901234567890123456789012345678901234567890123456789012345678901234" # x
`,
},
{
name: "sort fields with null value",
from: `apiVersion: v1
kind: ConfigMap
metadata:
name: workspaces.app.terraform.io
creationTimestamp: null # this field is null
namespace: staging
`,
to: `apiVersion: v1
kind: ConfigMap
metadata:
name: workspaces.app.terraform.io
creationTimestamp: null
namespace: staging
`,
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: workspaces.app.terraform.io
creationTimestamp: null # this field is null
namespace: staging
`,
},
}
@@ -378,9 +402,18 @@ data:
t.FailNow()
}
actualFrom, err := from.String()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(actual)) {
t.FailNow()
}
if !assert.Equal(t, strings.TrimSpace(tc.from), strings.TrimSpace(actualFrom)) {
t.FailNow()
}
})
}
}

View File

@@ -128,6 +128,7 @@ metadata:
annotations:
key: foo-a
config.kubernetes.io/index: '0'
internal.config.kubernetes.io/index: '0'
labels:
key: foo-l
`
@@ -141,6 +142,7 @@ metadata:
annotations:
key: bar-a
config.kubernetes.io/index: '1'
internal.config.kubernetes.io/index: '1'
labels:
key: bar-l
`

View File

@@ -194,6 +194,8 @@ metadata:
name: deployment-foo
annotations:
config.kubernetes.io/index: '0'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'statefulset_deployment-foo.yaml'
config.kubernetes.io/path: 'statefulset_deployment-foo.yaml'
---
apiVersion: v1
@@ -202,6 +204,8 @@ metadata:
name: service-foo
annotations:
config.kubernetes.io/index: '1'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'service_service-foo.yaml'
config.kubernetes.io/path: 'service_service-foo.yaml'
`, b.String()) {
t.FailNow()

View File

@@ -43,6 +43,7 @@ kind: StatefulSet
metadata:
name: deployment-foo
annotations:
internal.config.kubernetes.io/path: 'statefulset_deployment-foo.yaml'
config.kubernetes.io/path: 'statefulset_deployment-foo.yaml'
`,
`apiVersion: v1
@@ -50,6 +51,7 @@ kind: Service
metadata:
name: service-foo
annotations:
internal.config.kubernetes.io/path: 'service_service-foo.yaml'
config.kubernetes.io/path: 'service_service-foo.yaml'
`,
},

View File

@@ -66,9 +66,14 @@ func (c *FunctionFilter) getFunctionScope() (string, error) {
if err != nil {
return "", errors.Wrap(err)
}
p, found := m.Annotations[kioutil.PathAnnotation]
var p string
var found bool
p, found = m.Annotations[kioutil.PathAnnotation]
if !found {
return "", nil
p, found = m.Annotations[kioutil.LegacyPathAnnotation]
if !found {
return "", nil
}
}
functionDir := path.Clean(path.Dir(p))
@@ -101,12 +106,17 @@ func (c *FunctionFilter) scope(dir string, nodes []*yaml.RNode) ([]*yaml.RNode,
if err != nil {
return nil, nil, err
}
p, found := m.Annotations[kioutil.PathAnnotation]
var p string
var found bool
p, found = m.Annotations[kioutil.PathAnnotation]
if !found {
// this Resource isn't scoped under the function -- don't know where it came from
// consider it out of scope
saved = append(saved, nodes[i])
continue
p, found = m.Annotations[kioutil.LegacyPathAnnotation]
if !found {
// this Resource isn't scoped under the function -- don't know where it came from
// consider it out of scope
saved = append(saved, nodes[i])
continue
}
}
resourceDir := path.Clean(path.Dir(p))
@@ -193,8 +203,6 @@ func (c *FunctionFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
return append(output, saved...), nil
}
const idAnnotation = "config.k8s.io/id"
func (c *FunctionFilter) setIds(nodes []*yaml.RNode) error {
// set the id on each node to map inputs to outputs
var id int
@@ -202,7 +210,11 @@ func (c *FunctionFilter) setIds(nodes []*yaml.RNode) error {
for i := range nodes {
id++
idStr := fmt.Sprintf("%v", id)
err := nodes[i].PipeE(yaml.SetAnnotation(idAnnotation, idStr))
err := nodes[i].PipeE(yaml.SetAnnotation(kioutil.IdAnnotation, idStr))
if err != nil {
return errors.Wrap(err)
}
err = nodes[i].PipeE(yaml.SetAnnotation(kioutil.LegacyIdAnnotation, idStr))
if err != nil {
return errors.Wrap(err)
}
@@ -214,12 +226,18 @@ func (c *FunctionFilter) setIds(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))
anID, err := node.Pipe(yaml.GetAnnotation(kioutil.IdAnnotation))
if err != nil {
return errors.Wrap(err)
}
if anID == nil {
continue
anID, err = node.Pipe(yaml.GetAnnotation(kioutil.LegacyIdAnnotation))
if err != nil {
return errors.Wrap(err)
}
if anID == nil {
continue
}
}
var in *yaml.RNode
@@ -233,7 +251,10 @@ func (c *FunctionFilter) copyCommentsAndSyncOrder(nodes []*yaml.RNode) error {
if err := order.SyncOrder(in, node); err != nil {
return errors.Wrap(err)
}
if err := node.PipeE(yaml.ClearAnnotation(idAnnotation)); err != nil {
if err := node.PipeE(yaml.ClearAnnotation(kioutil.IdAnnotation)); err != nil {
return errors.Wrap(err)
}
if err := node.PipeE(yaml.ClearAnnotation(kioutil.LegacyIdAnnotation)); err != nil {
return errors.Wrap(err)
}
}

View File

@@ -82,6 +82,7 @@ kind: Deployment
metadata:
name: deployment-foo
annotations:
internal.config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
`,
`
@@ -90,6 +91,7 @@ kind: Service
metadata:
name: service-foo
annotations:
internal.config.kubernetes.io/path: 'service_service-foo.yaml'
config.kubernetes.io/path: 'service_service-foo.yaml'
`,
},
@@ -123,6 +125,7 @@ kind: Deployment
metadata:
name: deployment-foo
annotations:
internal.config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
`,
`
@@ -132,6 +135,7 @@ metadata:
name: service-foo
annotations:
config.kubernetes.io/path: 'foo.yaml'
internal.config.kubernetes.io/path: 'foo.yaml'
`,
},
},
@@ -180,6 +184,7 @@ metadata:
name: service-foo
annotations:
config.kubernetes.io/path: 'foo.yaml'
internal.config.kubernetes.io/path: 'foo.yaml'
`,
`
apiVersion: v1
@@ -188,6 +193,7 @@ metadata:
name: configmap-foo
annotations:
config.kubernetes.io/path: 'foo.yaml'
internal.config.kubernetes.io/path: 'foo.yaml'
`,
},
},
@@ -235,6 +241,7 @@ kind: Deployment
metadata:
name: deployment-foo
annotations:
internal.config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
`, `
apiVersion: v1
@@ -242,6 +249,7 @@ kind: Service
metadata:
name: service-foo
annotations:
internal.config.kubernetes.io/path: 'service_service-foo.yaml'
config.kubernetes.io/path: 'service_service-foo.yaml'
`,
},
@@ -393,6 +401,7 @@ kind: Deployment
metadata:
name: deployment-foo
annotations:
internal.config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
config.kubernetes.io/path: 'deployment_deployment-foo.yaml'
`, `
apiVersion: v1
@@ -400,6 +409,7 @@ kind: Service
metadata:
name: service-foo
annotations:
internal.config.kubernetes.io/path: 'service_service-foo.yaml'
config.kubernetes.io/path: 'service_service-foo.yaml'
`,
},
@@ -486,6 +496,7 @@ items:
name: service-foo
annotations:
config.kubernetes.io/path: 'foo/bar/s.yaml'
internal.config.kubernetes.io/id: '1'
config.k8s.io/id: '1'
functionConfig:
apiVersion: example.com/v1
@@ -505,7 +516,7 @@ items:
annotations:
config.kubernetes.io/path: 'foo/bar/s.yaml'
new: annotation
config.k8s.io/id: '1'
internal.config.kubernetes.io/id: '1'
functionConfig:
apiVersion: example.com/v1
kind: Example
@@ -551,6 +562,7 @@ metadata:
annotations:
config.kubernetes.io/path: 'foo/bar/s.yaml'
new: annotation
internal.config.kubernetes.io/path: 'foo/bar/s.yaml'
`, `
apiVersion: apps/v1
kind: Deployment
@@ -575,6 +587,7 @@ items:
name: service-foo
annotations:
config.kubernetes.io/path: 'foo/bar/s.yaml'
internal.config.kubernetes.io/id: '1'
config.k8s.io/id: '1'
functionConfig:
apiVersion: example.com/v1
@@ -594,7 +607,7 @@ items:
annotations:
config.kubernetes.io/path: 'foo/bar/s.yaml'
new: annotation
config.k8s.io/id: '1'
internal.config.kubernetes.io/id: '1'
functionConfig:
apiVersion: example.com/v1
kind: Example
@@ -638,6 +651,7 @@ metadata:
annotations:
config.kubernetes.io/path: 'foo/bar/s.yaml'
new: annotation
internal.config.kubernetes.io/path: 'foo/bar/s.yaml'
`, `
apiVersion: apps/v1
kind: Deployment
@@ -661,6 +675,7 @@ items:
name: deployment-foo
annotations:
config.kubernetes.io/path: 'baz/bar/d.yaml'
internal.config.kubernetes.io/id: '1'
config.k8s.io/id: '1'
- apiVersion: v1
kind: Service
@@ -668,6 +683,7 @@ items:
name: service-foo
annotations:
config.kubernetes.io/path: 'foo/bar/s.yaml'
internal.config.kubernetes.io/id: '2'
config.k8s.io/id: '2'
functionConfig:
apiVersion: example.com/v1
@@ -686,7 +702,7 @@ items:
name: deployment-foo
annotations:
config.kubernetes.io/path: 'baz/bar/d.yaml'
config.k8s.io/id: '1'
internal.config.kubernetes.io/id: '1'
- apiVersion: v1
kind: Service
metadata:
@@ -737,6 +753,7 @@ metadata:
name: deployment-foo
annotations:
config.kubernetes.io/path: 'baz/bar/d.yaml'
internal.config.kubernetes.io/path: 'baz/bar/d.yaml'
`, `
apiVersion: v1
kind: Service
@@ -745,6 +762,7 @@ metadata:
annotations:
config.kubernetes.io/path: 'foo/bar/s.yaml'
new: annotation
internal.config.kubernetes.io/path: 'foo/bar/s.yaml'
`,
},
},
@@ -831,6 +849,7 @@ items:
name: service-foo
annotations:
config.kubernetes.io/path: 'foo/bar/s.yaml'
internal.config.kubernetes.io/id: '1'
config.k8s.io/id: '1'
functionConfig:
apiVersion: example.com/v1
@@ -849,8 +868,9 @@ items:
name: service-foo
annotations:
config.kubernetes.io/path: 'foo/bar/s.yaml'
config.k8s.io/id: '1'
internal.config.kubernetes.io/id: '1'
new: annotation
internal.config.kubernetes.io/path: 'foo/bar/s.yaml'
functionConfig:
apiVersion: example.com/v1
kind: Example
@@ -896,6 +916,7 @@ metadata:
annotations:
config.kubernetes.io/path: 'foo/bar/s.yaml'
new: annotation
internal.config.kubernetes.io/path: 'foo/bar/s.yaml'
`, `
apiVersion: apps/v1
kind: Deployment
@@ -919,6 +940,7 @@ items:
name: deployment-foo
annotations:
config.kubernetes.io/path: 'foo/b.yaml'
internal.config.kubernetes.io/id: '1'
config.k8s.io/id: '1'
- apiVersion: v1
kind: Service
@@ -926,6 +948,7 @@ items:
name: service-foo # name comment
annotations:
config.kubernetes.io/path: 'foo/a.yaml'
internal.config.kubernetes.io/id: '2'
config.k8s.io/id: '2'
functionConfig:
apiVersion: example.com/v1
@@ -945,14 +968,14 @@ items:
name: deployment-foo
annotations:
config.kubernetes.io/path: 'foo/b.yaml'
config.k8s.io/id: '1'
internal.config.kubernetes.io/id: '1'
- apiVersion: v1
kind: Service
metadata:
name: service-foo
annotations:
config.kubernetes.io/path: 'foo/a.yaml'
config.k8s.io/id: '2'
internal.config.kubernetes.io/id: '2'
new: annotation
functionConfig:
apiVersion: example.com/v1
@@ -996,6 +1019,7 @@ metadata:
name: deployment-foo
annotations:
config.kubernetes.io/path: 'foo/b.yaml'
internal.config.kubernetes.io/path: 'foo/b.yaml'
`, `
apiVersion: v1
kind: Service
@@ -1004,6 +1028,7 @@ metadata:
annotations:
config.kubernetes.io/path: 'foo/a.yaml'
new: annotation
internal.config.kubernetes.io/path: 'foo/a.yaml'
`,
},
},

View File

@@ -14,6 +14,7 @@ import (
"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
"sigs.k8s.io/kustomize/kyaml/fn/runtime/starlark"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
@@ -76,6 +77,7 @@ run(ctx.resource_list["items"])
// name: deployment-1
// annotations:
// foo: bar
// internal.config.kubernetes.io/path: 'deployment_deployment-1.yaml'
// config.kubernetes.io/path: 'deployment_deployment-1.yaml'
// spec:
// template:
@@ -90,6 +92,7 @@ run(ctx.resource_list["items"])
// name: deployment-2
// annotations:
// foo: bar
// internal.config.kubernetes.io/path: 'deployment_deployment-2.yaml'
// config.kubernetes.io/path: 'deployment_deployment-2.yaml'
// spec:
// template:
@@ -168,6 +171,7 @@ run(ctx.resource_list["items"], ctx.resource_list["functionConfig"]["spec"]["val
// name: deployment-1
// annotations:
// foo: hello world
// internal.config.kubernetes.io/path: 'deployment_deployment-1.yaml'
// config.kubernetes.io/path: 'deployment_deployment-1.yaml'
// spec:
// template:
@@ -182,6 +186,7 @@ run(ctx.resource_list["items"], ctx.resource_list["functionConfig"]["spec"]["val
// name: deployment-2
// annotations:
// foo: hello world
// internal.config.kubernetes.io/path: 'deployment_deployment-2.yaml'
// config.kubernetes.io/path: 'deployment_deployment-2.yaml'
// spec:
// template:
@@ -257,8 +262,11 @@ run(ctx.resource_list["items"])
Inputs: []kio.Reader{&kio.LocalPackageReader{PackagePath: d}},
Filters: []kio.Filter{fltr},
Outputs: []kio.Writer{&kio.ByteWriter{
Writer: output,
ClearAnnotations: []string{"config.kubernetes.io/path"},
Writer: output,
ClearAnnotations: []string{
kioutil.PathAnnotation,
kioutil.LegacyPathAnnotation,
},
}}}.Execute()
if err != nil {
log.Println(err)

View File

@@ -56,6 +56,7 @@ metadata:
name: nginx-deployment
annotations:
foo: bar
internal.config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
spec:
template:
@@ -96,6 +97,7 @@ metadata:
name: nginx-deployment
annotations:
foo: annotation-value
internal.config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
spec:
template:
@@ -135,6 +137,7 @@ metadata:
name: nginx-deployment
annotations:
foo: Deployment enables declarative updates for Pods and ReplicaSets.
internal.config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
spec:
template:
@@ -177,6 +180,7 @@ metadata:
name: nginx-deployment
annotations:
foo: bar
internal.config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
spec:
template:
@@ -218,6 +222,7 @@ kind: Deployment
metadata:
name: nginx-deployment
annotations:
internal.config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
spec:
template:
@@ -272,6 +277,7 @@ metadata:
name: nginx-deployment-1
annotations:
foo: bar
internal.config.kubernetes.io/path: 'deployment_nginx-deployment-1.yaml'
config.kubernetes.io/path: 'deployment_nginx-deployment-1.yaml'
spec:
template:
@@ -287,6 +293,7 @@ metadata:
name: nginx-deployment-2
annotations:
foo: bar
internal.config.kubernetes.io/path: 'deployment_nginx-deployment-2.yaml'
config.kubernetes.io/path: 'deployment_nginx-deployment-2.yaml'
spec:
template:
@@ -329,6 +336,7 @@ kind: Deployment
metadata:
name: nginx-deployment-1
annotations:
internal.config.kubernetes.io/path: 'deployment_nginx-deployment-1.yaml'
config.kubernetes.io/path: 'deployment_nginx-deployment-1.yaml'
spec:
template:
@@ -343,6 +351,7 @@ kind: Deployment
metadata:
name: nginx-deployment-2
annotations:
internal.config.kubernetes.io/path: 'deployment_nginx-deployment-2.yaml'
config.kubernetes.io/path: 'deployment_nginx-deployment-2.yaml'
`,
},
@@ -370,6 +379,7 @@ kind: Deployment
metadata:
name: nginx-deployment-1
annotations:
internal.config.kubernetes.io/path: 'deployment_nginx-deployment-1.yaml'
config.kubernetes.io/path: 'deployment_nginx-deployment-1.yaml'
`,
},
@@ -409,6 +419,7 @@ metadata:
name: nginx-deployment
annotations:
foo: hello world
internal.config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
spec:
template:
@@ -462,6 +473,7 @@ metadata:
name: nginx-deployment
annotations:
foo: hello world
internal.config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
config.kubernetes.io/path: 'deployment_nginx-deployment.yaml'
spec:
template:

View File

@@ -51,7 +51,15 @@ func WrapErrorWithFile(err error, meta yaml.ResourceMeta) error {
if err == nil {
return err
}
path := meta.Annotations[kioutil.PathAnnotation]
index := meta.Annotations[kioutil.IndexAnnotation]
if path == "" {
path = meta.Annotations[kioutil.LegacyPathAnnotation]
}
if index == "" {
index = meta.Annotations[kioutil.LegacyPathAnnotation]
}
return errors.WrapPrefixf(err, "%s [%s]",
meta.Annotations[kioutil.PathAnnotation],
meta.Annotations[kioutil.IndexAnnotation])
meta.Annotations[path],
meta.Annotations[index])
}

View File

@@ -43,6 +43,13 @@ type ByteReadWriter struct {
// Style is a style that is set on the Resource Node Document.
Style yaml.Style
// WrapBareSeqNode wraps the bare sequence node document with map node,
// kyaml uses reader annotations to track resources, it is not possible to
// add them to bare sequence nodes, this option enables wrapping such bare
// sequence nodes into map node with key yaml.BareSeqNodeWrappingKey
// note that this wrapping is different and not related to ResourceList wrapping
WrapBareSeqNode bool
FunctionConfig *yaml.RNode
Results *yaml.RNode
@@ -57,6 +64,7 @@ func (rw *ByteReadWriter) Read() ([]*yaml.RNode, error) {
Reader: rw.Reader,
OmitReaderAnnotations: rw.OmitReaderAnnotations,
PreserveSeqIndent: rw.PreserveSeqIndent,
WrapBareSeqNode: rw.WrapBareSeqNode,
}
val, err := b.Read()
if rw.FunctionConfig == nil {
@@ -94,6 +102,7 @@ func ParseAll(inputs ...string) ([]*yaml.RNode, error) {
func FromBytes(bs []byte) ([]*yaml.RNode, error) {
return (&ByteReader{
OmitReaderAnnotations: true,
AnchorsAweigh: true,
Reader: bytes.NewBuffer(bs),
}).Read()
}
@@ -137,6 +146,17 @@ type ByteReader struct {
// WrappingKind is set by Read(), and is the kind of the object that
// the read objects were originally wrapped in.
WrappingKind string
// WrapBareSeqNode wraps the bare sequence node document with map node,
// kyaml uses reader annotations to track resources, it is not possible to
// add them to bare sequence nodes, this option enables wrapping such bare
// sequence nodes into map node with key yaml.BareSeqNodeWrappingKey
// note that this wrapping is different and not related to ResourceList wrapping
WrapBareSeqNode bool
// AnchorsAweigh set to true attempts to replace all YAML anchor aliases
// with their definitions (anchor values) immediately after the read.
AnchorsAweigh bool
}
var _ Reader = &ByteReader{}
@@ -254,6 +274,13 @@ func (r *ByteReader) Read() ([]*yaml.RNode, error) {
// increment the index annotation value
index++
}
if r.AnchorsAweigh {
for _, n := range output {
if err = n.DeAnchor(); err != nil {
return nil, err
}
}
}
return output, nil
}
@@ -275,11 +302,22 @@ func (r *ByteReader) decode(originalYAML string, index int, decoder *yaml.Decode
// sort the annotations by key so the output Resources is consistent (otherwise the
// annotations will be in a random order)
n := yaml.NewRNode(node)
// check if it is a bare sequence node and wrap it with a yaml.BareSeqNodeWrappingKey
if r.WrapBareSeqNode && node.Kind == yaml.DocumentNode && len(node.Content) > 0 &&
node.Content[0] != nil && node.Content[0].Kind == yaml.SequenceNode {
wrappedNode := yaml.NewRNode(&yaml.Node{
Kind: yaml.MappingNode,
})
wrappedNode.PipeE(yaml.SetField(yaml.BareSeqNodeWrappingKey, n))
n = wrappedNode
}
if r.SetAnnotations == nil {
r.SetAnnotations = map[string]string{}
}
if !r.OmitReaderAnnotations {
r.SetAnnotations[kioutil.IndexAnnotation] = fmt.Sprintf("%d", index)
r.SetAnnotations[kioutil.LegacyIndexAnnotation] = fmt.Sprintf("%d", index)
if r.PreserveSeqIndent {
// derive and add the seqindent annotation
@@ -300,5 +338,5 @@ func (r *ByteReader) decode(originalYAML string, index int, decoder *yaml.Decode
return nil, errors.Wrap(err)
}
}
return yaml.NewRNode(node), nil
return n, nil
}

View File

@@ -182,6 +182,7 @@ c: d
metadata:
annotations:
config.kubernetes.io/index: '0'
internal.config.kubernetes.io/index: '0'
`,
`# second resource
e: f
@@ -190,11 +191,13 @@ g:
metadata:
annotations:
config.kubernetes.io/index: '1'
internal.config.kubernetes.io/index: '1'
`,
`i: j
metadata:
annotations:
config.kubernetes.io/index: '2'
internal.config.kubernetes.io/index: '2'
`,
},
},
@@ -260,6 +263,7 @@ c: d
metadata:
annotations:
config.kubernetes.io/index: '0'
internal.config.kubernetes.io/index: '0'
`,
`
# second resource
@@ -269,12 +273,14 @@ g:
metadata:
annotations:
config.kubernetes.io/index: '1'
internal.config.kubernetes.io/index: '1'
`,
`
i: j
metadata:
annotations:
config.kubernetes.io/index: '2'
internal.config.kubernetes.io/index: '2'
`,
},
instance: ByteReader{},
@@ -369,7 +375,7 @@ metadata:
`,
expectedItems: []string{
`
{"a": "b", "c": [1, 2], metadata: {annotations: {config.kubernetes.io/index: '0'}}}
{"a": "b", "c": [1, 2], metadata: {annotations: {config.kubernetes.io/index: '0', internal.config.kubernetes.io/index: '0'}}}
`,
},
instance: ByteReader{},
@@ -758,7 +764,7 @@ items:
metadata:
name: deployment-b
spec:
<<: *hostAliases
*hostAliases
`),
exp: expected{
sOut: []string{`
@@ -769,7 +775,7 @@ items:
kind: Deployment
metadata:
name: deployment-a
spec: &hostAliases
spec:
template:
spec:
hostAliases:
@@ -781,7 +787,12 @@ items:
metadata:
name: deployment-b
spec:
!!merge <<: *hostAliases
template:
spec:
hostAliases:
- hostnames:
- a.example.com
ip: 8.8.8.8
`},
},
},
@@ -808,27 +819,21 @@ items:
}
}
// This test shows the lower level (go-yaml) representation of a small doc
// with an anchor. The anchor structure is there, in the sense that an
// alias pointer is readily available when a node's kind is an AliasNode.
// I.e. the anchor mapping name -> object was noted during unmarshalling.
// However, at the time of writing github.com/go-yaml/yaml/encoder.go
// doesn't appear to have an option to perform anchor replacements when
// encoding. It emits anchor definitions and references (aliases) intact.
func TestByteReader_AnchorBehavior(t *testing.T) {
// Show the low level (go-yaml) representation of a small doc with a
// YAML anchor and alias after reading it with anchor expansion on or off.
func TestByteReader_AnchorsAweigh(t *testing.T) {
const input = `
data:
color: &color-used blue
feeling: *color-used
`
expected := strings.TrimSpace(`
data:
color: &color-used blue
feeling: *color-used
`)
var rNode *yaml.RNode
{
rNodes, err := FromBytes([]byte(input))
rNodes, err := (&ByteReader{
OmitReaderAnnotations: true,
AnchorsAweigh: false,
Reader: bytes.NewBuffer([]byte(input)),
}).Read()
assert.NoError(t, err)
assert.Equal(t, 1, len(rNodes))
rNode = rNodes[0]
@@ -857,7 +862,7 @@ data:
assert.Equal(t, yaml.ScalarNode, yNodes[0].Kind)
assert.Equal(t, yaml.NodeTagString, yNodes[0].Tag)
assert.Equal(t, "color", yNodes[0].Value)
assert.Equal(t, "", yNodes[0].Anchor)
assert.Empty(t, yNodes[0].Anchor)
assert.Nil(t, yNodes[0].Alias)
assert.Equal(t, yaml.ScalarNode, yNodes[1].Kind)
@@ -869,19 +874,82 @@ data:
assert.Equal(t, yaml.ScalarNode, yNodes[2].Kind)
assert.Equal(t, yaml.NodeTagString, yNodes[2].Tag)
assert.Equal(t, "feeling", yNodes[2].Value)
assert.Equal(t, "", yNodes[2].Anchor)
assert.Empty(t, yNodes[2].Anchor)
assert.Nil(t, yNodes[2].Alias)
assert.Equal(t, yaml.AliasNode, yNodes[3].Kind)
assert.Equal(t, "", yNodes[3].Tag)
assert.Empty(t, yNodes[3].Tag)
assert.Equal(t, "color-used", yNodes[3].Value)
assert.Equal(t, "", yNodes[3].Anchor)
assert.Empty(t, yNodes[3].Anchor)
assert.NotNil(t, yNodes[3].Alias)
}
yaml, err := rNode.String()
str, err := rNode.String()
assert.NoError(t, err)
assert.Equal(t, expected, strings.TrimSpace(yaml))
// The string version matches the input (it still has anchors and aliases).
assert.Equal(t, strings.TrimSpace(input), strings.TrimSpace(str))
// Now do same thing again, but this time set AnchorsAweigh = true.
{
rNodes, err := (&ByteReader{
OmitReaderAnnotations: true,
AnchorsAweigh: true,
Reader: bytes.NewBuffer([]byte(input)),
}).Read()
assert.NoError(t, err)
assert.Equal(t, 1, len(rNodes))
rNode = rNodes[0]
}
// Again make assertions on the internals.
{
yNode := rNode.YNode()
assert.Equal(t, yaml.NodeTagMap, yNode.Tag)
yNodes := yNode.Content
assert.Equal(t, 2, len(yNodes))
assert.Equal(t, yaml.NodeTagString, yNodes[0].Tag)
assert.Equal(t, "data", yNodes[0].Value)
assert.Equal(t, yaml.NodeTagMap, yNodes[1].Tag)
yNodes = yNodes[1].Content
assert.Equal(t, 4, len(yNodes))
assert.Equal(t, yaml.ScalarNode, yNodes[0].Kind)
assert.Equal(t, yaml.NodeTagString, yNodes[0].Tag)
assert.Equal(t, "color", yNodes[0].Value)
assert.Empty(t, yNodes[0].Anchor)
assert.Nil(t, yNodes[0].Alias)
assert.Equal(t, yaml.ScalarNode, yNodes[1].Kind)
assert.Equal(t, yaml.NodeTagString, yNodes[1].Tag)
assert.Equal(t, "blue", yNodes[1].Value)
assert.Empty(t, yNodes[1].Anchor)
assert.Nil(t, yNodes[1].Alias)
assert.Equal(t, yaml.ScalarNode, yNodes[2].Kind)
assert.Equal(t, yaml.NodeTagString, yNodes[2].Tag)
assert.Equal(t, "feeling", yNodes[2].Value)
assert.Empty(t, yNodes[2].Anchor)
assert.Nil(t, yNodes[2].Alias)
assert.Equal(t, yaml.ScalarNode, yNodes[3].Kind)
assert.Equal(t, yaml.NodeTagString, yNodes[3].Tag)
assert.Equal(t, "blue", yNodes[3].Value)
assert.Empty(t, yNodes[3].Anchor)
assert.Nil(t, yNodes[3].Alias)
}
str, err = rNode.String()
assert.NoError(t, err)
// This time, the alias has been replaced with the anchor definition.
assert.Equal(t, strings.TrimSpace(`
data:
color: blue
feeling: blue
`), strings.TrimSpace(str))
}
// TestByteReader_AddSeqIndentAnnotation tests if the internal.config.kubernetes.io/seqindent

View File

@@ -216,6 +216,7 @@ spec:
metadata:
annotations:
config.kubernetes.io/index: '0'
internal.config.kubernetes.io/index: '0'
---
kind: Service
spec:
@@ -224,6 +225,7 @@ spec:
metadata:
annotations:
config.kubernetes.io/index: '1'
internal.config.kubernetes.io/index: '1'
`,
instance: kio.ByteReadWriter{KeepReaderAnnotations: true},
},
@@ -589,3 +591,159 @@ env:
})
}
}
func TestByteReadWriter_WrapBareSeqNode(t *testing.T) {
type testCase struct {
name string
readerErr string
writerErr string
input string
wrapBareSeqNode bool
expectedOutput string
instance kio.ByteReadWriter
}
testCases := []testCase{
{
name: "round_trip bare seq node simple",
wrapBareSeqNode: true,
input: `
- foo
- bar
`,
expectedOutput: `
- foo
- bar
`,
},
{
name: "round_trip bare seq node",
wrapBareSeqNode: true,
input: `# Use the old CRD because of the quantity validation issue:
# https://github.com/kubeflow/kubeflow/issues/5722
- op: replace
path: /spec
value:
group: kubeflow.org
names:
kind: Notebook
plural: notebooks
singular: notebook
scope: Namespaced
subresources:
status: {}
versions:
- name: v1alpha1
served: true
storage: false
`,
expectedOutput: `# Use the old CRD because of the quantity validation issue:
# https://github.com/kubeflow/kubeflow/issues/5722
- op: replace
path: /spec
value:
group: kubeflow.org
names:
kind: Notebook
plural: notebooks
singular: notebook
scope: Namespaced
subresources:
status: {}
versions:
- name: v1alpha1
served: true
storage: false
`,
},
{
name: "error round_trip bare seq node simple",
wrapBareSeqNode: false,
input: `
- foo
- bar
`,
readerErr: "wrong Node Kind for expected: MappingNode was SequenceNode",
},
{
name: "error round_trip bare seq node",
wrapBareSeqNode: false,
input: `# Use the old CRD because of the quantity validation issue:
# https://github.com/kubeflow/kubeflow/issues/5722
- op: replace
path: /spec
value:
group: kubeflow.org
names:
kind: Notebook
plural: notebooks
singular: notebook
scope: Namespaced
subresources:
status: {}
versions:
- name: v1alpha1
served: true
storage: false
`,
readerErr: "wrong Node Kind for expected: MappingNode was SequenceNode",
},
{
name: "round_trip bare seq node json",
wrapBareSeqNode: true,
input: `[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--namespaced"}]`,
expectedOutput: `[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--namespaced"}]`,
},
{
name: "error round_trip invalid yaml node",
wrapBareSeqNode: false,
input: "I am not valid",
readerErr: "wrong Node Kind for expected: MappingNode was ScalarNode",
},
}
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
w.WrapBareSeqNode = tc.wrapBareSeqNode
nodes, err := w.Read()
if tc.readerErr != "" {
if !assert.Error(t, err) {
t.FailNow()
}
if !assert.Contains(t, err.Error(), tc.readerErr) {
t.FailNow()
}
return
}
w.WrappingKind = ""
err = w.Write(nodes)
if !assert.NoError(t, err) {
t.FailNow()
}
if tc.writerErr != "" {
if !assert.Error(t, err) {
t.FailNow()
}
if !assert.Contains(t, err.Error(), tc.writerErr) {
t.FailNow()
}
return
}
if !assert.Equal(t,
strings.TrimSpace(tc.expectedOutput), strings.TrimSpace(out.String())) {
t.FailNow()
}
})
}
}

View File

@@ -7,6 +7,7 @@ import (
"encoding/json"
"io"
"path/filepath"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
@@ -75,6 +76,10 @@ func (w ByteWriter) Write(inputNodes []*yaml.RNode) error {
if err != nil {
return errors.Wrap(err)
}
_, err = nodes[i].Pipe(yaml.ClearAnnotation(kioutil.LegacyIndexAnnotation))
if err != nil {
return errors.Wrap(err)
}
_, err = nodes[i].Pipe(yaml.ClearAnnotation(kioutil.SeqIndentAnnotation))
if err != nil {
@@ -113,7 +118,7 @@ func (w ByteWriter) Write(inputNodes []*yaml.RNode) error {
} else {
encoder.CompactSeqIndent()
}
if err := encoder.Encode(nodes[i].Document()); err != nil {
if err := encoder.Encode(upWrapBareSequenceNode(nodes[i].Document())); err != nil {
return errors.Wrap(err)
}
}
@@ -181,3 +186,13 @@ func (w ByteWriter) shouldJSONEncodeSingleBareNode(nodes []*yaml.RNode) bool {
}
return false
}
// upWrapBareSequenceNode unwraps the bare sequence nodes wrapped by yaml.BareSeqNodeWrappingKey
func upWrapBareSequenceNode(node *yaml.Node) *yaml.Node {
rNode := yaml.NewRNode(node)
seqNode, err := rNode.Pipe(yaml.Lookup(yaml.BareSeqNodeWrappingKey))
if err == nil && !seqNode.IsNilOrEmpty() {
return seqNode.YNode()
}
return node
}

View File

@@ -222,8 +222,8 @@ spec:
`a: b #first
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/index: 0
internal.config.kubernetes.io/path: "a/b/a_test.yaml"
`,
`e: f
g:
@@ -232,28 +232,32 @@ g:
- j
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/b_test.yaml"
internal.config.kubernetes.io/index: 0
internal.config.kubernetes.io/path: "a/b/b_test.yaml"
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: 1
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/index: 1
internal.config.kubernetes.io/path: "a/b/a_test.yaml"
`,
},
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: 0
internal.config.kubernetes.io/path: "a/b/a_test.yaml"
config.kubernetes.io/path: 'a/b/a_test.yaml'
config.kubernetes.io/index: '0'
---
c: d # second
metadata:
annotations:
config.kubernetes.io/index: 1
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/index: 1
internal.config.kubernetes.io/path: "a/b/a_test.yaml"
config.kubernetes.io/path: 'a/b/a_test.yaml'
config.kubernetes.io/index: '1'
---
e: f
g:
@@ -262,8 +266,10 @@ g:
- j
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/b_test.yaml"
internal.config.kubernetes.io/index: 0
internal.config.kubernetes.io/path: "a/b/b_test.yaml"
config.kubernetes.io/path: 'a/b/b_test.yaml'
config.kubernetes.io/index: '0'
`,
},
@@ -277,13 +283,13 @@ metadata:
`a: b #first
metadata:
annotations:
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/path: "a/b/a_test.yaml"
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: 1
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/index: 1
internal.config.kubernetes.io/path: "a/b/a_test.yaml"
`,
`e: f
g:
@@ -293,21 +299,23 @@ g:
`,
},
expectedOutput: `e: f
g:
h:
- i # has a list
- j
---
a: b #first
expectedOutput: `a: b #first
metadata:
annotations:
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/path: "a/b/a_test.yaml"
config.kubernetes.io/path: 'a/b/a_test.yaml'
---
c: d # second
metadata:
annotations:
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/path: "a/b/a_test.yaml"
config.kubernetes.io/path: 'a/b/a_test.yaml'
---
e: f
g:
h:
- i # has a list
- j
`,
},
@@ -321,8 +329,8 @@ metadata:
`a: b #first
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/index: 0
internal.config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/index: "compact"
`,
`e: f
@@ -332,15 +340,15 @@ g:
- j
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/b_test.yaml"
internal.config.kubernetes.io/index: 0
internal.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/index: 1
internal.config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/seqindent: "compact"
`,
},
@@ -348,8 +356,8 @@ metadata:
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: 0
internal.config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/index: "compact"
---
e: f
@@ -359,15 +367,15 @@ g:
- j
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/path: "a/b/b_test.yaml"
internal.config.kubernetes.io/index: 0
internal.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/index: 1
internal.config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/seqindent: "compact"
`,
},
@@ -382,7 +390,7 @@ metadata:
"a": "a long string that would certainly see a newline introduced by the YAML marshaller abcd123",
metadata: {
annotations: {
config.kubernetes.io/path: test.json
internal.config.kubernetes.io/path: test.json
}
}
}`,
@@ -392,7 +400,8 @@ metadata:
"a": "a long string that would certainly see a newline introduced by the YAML marshaller abcd123",
"metadata": {
"annotations": {
"config.kubernetes.io/path": "test.json"
"config.kubernetes.io/path": "test.json",
"internal.config.kubernetes.io/path": "test.json"
}
}
}`,
@@ -409,8 +418,8 @@ metadata:
metadata: {
annotations: {
"internal.config.kubernetes.io/seqindent": "compact",
"config.kubernetes.io/index": "0",
"config.kubernetes.io/path": "test.json"
"internal.config.kubernetes.io/index": "0",
"internal.config.kubernetes.io/path": "test.json"
}
}
}`,
@@ -420,7 +429,8 @@ metadata:
"a": "a long string that would certainly see a newline introduced by the YAML marshaller abcd123",
"metadata": {
"annotations": {
"config.kubernetes.io/path": "test.json"
"config.kubernetes.io/path": "test.json",
"internal.config.kubernetes.io/path": "test.json"
}
}
}`,
@@ -432,14 +442,15 @@ metadata:
{
name: "encode_unformatted_valid_json",
items: []string{
`{ "a": "b", metadata: { annotations: { config.kubernetes.io/path: test.json } } }`,
`{ "a": "b", metadata: { annotations: { internal.config.kubernetes.io/path: test.json } } }`,
},
expectedOutput: `{
"a": "b",
"metadata": {
"annotations": {
"config.kubernetes.io/path": "test.json"
"config.kubernetes.io/path": "test.json",
"internal.config.kubernetes.io/path": "test.json"
}
}
}`,
@@ -460,7 +471,7 @@ metadata:
"a": "b",
"metadata": {
"annotations": {
"config.kubernetes.io/path": "test.json"
"internal.config.kubernetes.io/path": "test.json"
}
}
}`,
@@ -469,7 +480,7 @@ metadata:
expectedOutput: `apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- {"a": "b", "metadata": {"annotations": {"config.kubernetes.io/path": "test.json"}}}
- {"a": "b", "metadata": {"annotations": {"internal.config.kubernetes.io/path": "test.json"}}}
`,
},
@@ -483,7 +494,7 @@ items:
"a": "b",
"metadata": {
"annotations": {
"config.kubernetes.io/path": "test-1.json"
"internal.config.kubernetes.io/path": "test-1.json"
}
}
}`,
@@ -491,16 +502,16 @@ items:
"c": "d",
"metadata": {
"annotations": {
"config.kubernetes.io/path": "test-2.json"
"internal.config.kubernetes.io/path": "test-2.json"
}
}
}`,
},
expectedOutput: `
{"a": "b", "metadata": {"annotations": {"config.kubernetes.io/path": "test-1.json"}}}
{"a": "b", "metadata": {"annotations": {"internal.config.kubernetes.io/path": "test-1.json"}}}
---
{"c": "d", "metadata": {"annotations": {"config.kubernetes.io/path": "test-2.json"}}}
{"c": "d", "metadata": {"annotations": {"internal.config.kubernetes.io/path": "test-2.json"}}}
`,
},
}

View File

@@ -165,6 +165,10 @@ func (f *FileSetter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
resources := map[string][]*yaml.RNode{}
for i := range input {
if err := kioutil.CopyLegacyAnnotations(input[i]); err != nil {
return nil, err
}
m, err := input[i].GetMeta()
if err != nil {
return nil, err
@@ -178,6 +182,9 @@ func (f *FileSetter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
if _, err := input[i].Pipe(yaml.SetAnnotation(kioutil.PathAnnotation, file)); err != nil {
return nil, err
}
if _, err := input[i].Pipe(yaml.SetAnnotation(kioutil.LegacyPathAnnotation, file)); err != nil {
return nil, err
}
}
resources[file] = append(resources[file], input[i])
}
@@ -192,6 +199,10 @@ func (f *FileSetter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
yaml.SetAnnotation(kioutil.IndexAnnotation, fmt.Sprintf("%d", j))); err != nil {
return nil, err
}
if _, err := resources[i][j].Pipe(
yaml.SetAnnotation(kioutil.LegacyIndexAnnotation, fmt.Sprintf("%d", j))); err != nil {
return nil, err
}
output = append(output, resources[i][j])
}
}

View File

@@ -53,6 +53,7 @@ metadata:
name: foo1
namespace: bar
annotations:
internal.config.kubernetes.io/path: 'foo1_deployment.yaml'
config.kubernetes.io/path: 'foo1_deployment.yaml'
---
apiVersion: v1
@@ -60,6 +61,7 @@ kind: Service
metadata:
name: foo1
annotations:
internal.config.kubernetes.io/path: 'foo1_service.yaml'
config.kubernetes.io/path: 'foo1_service.yaml'
---
apiVersion: apps/v1
@@ -67,6 +69,7 @@ kind: Deployment
metadata:
name: foo2
annotations:
internal.config.kubernetes.io/path: 'foo2_deployment.yaml'
config.kubernetes.io/path: 'foo2_deployment.yaml'
---
apiVersion: v1
@@ -75,6 +78,7 @@ metadata:
name: foo2
namespace: bar
annotations:
internal.config.kubernetes.io/path: 'foo2_service.yaml'
config.kubernetes.io/path: 'foo2_service.yaml'
`, out.String())
}
@@ -97,6 +101,7 @@ kind: Service
metadata:
name: foo1
annotations:
internal.config.kubernetes.io/path: 'foo1__service.yaml'
config.kubernetes.io/path: 'foo1__service.yaml'
---
apiVersion: apps/v1
@@ -105,6 +110,7 @@ metadata:
name: foo1
namespace: bar
annotations:
internal.config.kubernetes.io/path: 'foo1_bar_deployment.yaml'
config.kubernetes.io/path: 'foo1_bar_deployment.yaml'
---
apiVersion: apps/v1
@@ -112,6 +118,7 @@ kind: Deployment
metadata:
name: foo2
annotations:
internal.config.kubernetes.io/path: 'foo2__deployment.yaml'
config.kubernetes.io/path: 'foo2__deployment.yaml'
---
apiVersion: v1
@@ -120,6 +127,7 @@ metadata:
name: foo2
namespace: bar
annotations:
internal.config.kubernetes.io/path: 'foo2_bar_service.yaml'
config.kubernetes.io/path: 'foo2_bar_service.yaml'
`, out.String())
}
@@ -143,6 +151,7 @@ metadata:
name: foo1
namespace: bar
annotations:
internal.config.kubernetes.io/path: 'resource.yaml'
config.kubernetes.io/path: 'resource.yaml'
---
apiVersion: apps/v1
@@ -150,6 +159,7 @@ kind: Deployment
metadata:
name: foo2
annotations:
internal.config.kubernetes.io/path: 'resource.yaml'
config.kubernetes.io/path: 'resource.yaml'
---
apiVersion: v1
@@ -158,6 +168,7 @@ metadata:
name: foo2
namespace: bar
annotations:
internal.config.kubernetes.io/path: 'resource.yaml'
config.kubernetes.io/path: 'resource.yaml'
---
apiVersion: v1
@@ -165,6 +176,7 @@ kind: Service
metadata:
name: foo1
annotations:
internal.config.kubernetes.io/path: 'resource.yaml'
config.kubernetes.io/path: 'resource.yaml'
`, out.String())
}

View File

@@ -173,6 +173,12 @@ func (dm *DefaultGVKNNMatcher) IsSameResource(node1, node2 *yaml.RNode) bool {
if node1 == nil || node2 == nil {
return false
}
if err := kioutil.CopyLegacyAnnotations(node1); err != nil {
return false
}
if err := kioutil.CopyLegacyAnnotations(node2); err != nil {
return false
}
meta1, err := node1.GetMeta()
if err != nil {

View File

@@ -6,7 +6,10 @@
package kio
import (
"fmt"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
@@ -113,6 +116,14 @@ func (p Pipeline) ExecuteWithCallback(callback PipelineExecuteCallbackFunc) erro
// apply operations
var err error
for i := range p.Filters {
// Not all RNodes passed through kio.Pipeline have metadata nor should
// they all be required to.
var nodeAnnos map[string]map[string]string
nodeAnnos, err = storeInternalAnnotations(result)
if err != nil && err != yaml.ErrMissingMetadata {
return err
}
op := p.Filters[i]
if callback != nil {
callback(op)
@@ -124,6 +135,13 @@ func (p Pipeline) ExecuteWithCallback(callback PipelineExecuteCallbackFunc) erro
if len(result) == 0 && !p.ContinueOnEmptyResult || err != nil {
return errors.Wrap(err)
}
// If either the internal annotations for path, index, and id OR the legacy
// annotations for path, index, and id are changed, we have to update the other.
err = reconcileInternalAnnotations(result, nodeAnnos)
if err != nil && err != yaml.ErrMissingMetadata {
return err
}
}
// write to the outputs
@@ -147,3 +165,196 @@ func FilterAll(filter yaml.Filter) Filter {
return nodes, nil
})
}
// Store the original path, index, and id annotations so that we can reconcile
// it later. This is necessary because currently both internal-prefixed annotations
// and legacy annotations are currently supported, and a change to one must be
// reflected in the other.
func storeInternalAnnotations(result []*yaml.RNode) (map[string]map[string]string, error) {
nodeAnnosMap := make(map[string]map[string]string)
for i := range result {
if err := kioutil.CopyLegacyAnnotations(result[i]); err != nil {
return nil, err
}
meta, err := result[i].GetMeta()
if err != nil {
return nil, err
}
path := meta.Annotations[kioutil.PathAnnotation]
index := meta.Annotations[kioutil.IndexAnnotation]
id := meta.Annotations[kioutil.IdAnnotation]
if _, ok := nodeAnnosMap[path]; !ok {
nodeAnnosMap[path] = make(map[string]string)
}
nodeAnnosMap[path][index] = id
}
return nodeAnnosMap, nil
}
type nodeAnnotations struct {
path string
index string
id string
}
func reconcileInternalAnnotations(result []*yaml.RNode, nodeAnnosMap map[string]map[string]string) error {
for _, node := range result {
meta, err := node.GetMeta()
if err != nil {
return err
}
// if only one annotation is set, set the other.
err = missingInternalOrLegacyAnnotations(node, meta)
if err != nil {
return err
}
// we must check to see if the function changed either the new internal annotations
// or the old legacy annotations. If one is changed, the change must be reflected
// in the other.
err = checkAnnotationsAltered(node, meta, nodeAnnosMap)
if err != nil {
return err
}
// if the annotations are still somehow out of sync, prefer the internal annotations
// and copy them to the legacy ones
err = kioutil.CopyLegacyAnnotations(node)
if err != nil {
return err
}
}
return nil
}
func missingInternalOrLegacyAnnotations(rn *yaml.RNode, meta yaml.ResourceMeta) error {
if err := missingInternalOrLegacyAnnotation(rn, meta, kioutil.PathAnnotation, kioutil.LegacyPathAnnotation); err != nil {
return err
}
if err := missingInternalOrLegacyAnnotation(rn, meta, kioutil.IndexAnnotation, kioutil.LegacyIndexAnnotation); err != nil {
return err
}
if err := missingInternalOrLegacyAnnotation(rn, meta, kioutil.IdAnnotation, kioutil.LegacyIdAnnotation); err != nil {
return err
}
return nil
}
func missingInternalOrLegacyAnnotation(rn *yaml.RNode, meta yaml.ResourceMeta, newKey string, legacyKey string) error {
value := meta.Annotations[newKey]
legacyValue := meta.Annotations[legacyKey]
if value == "" && legacyValue == "" {
// do nothing
return nil
}
if value == "" {
// new key is not set, copy from legacy key
if err := rn.PipeE(yaml.SetAnnotation(newKey, legacyValue)); err != nil {
return err
}
} else if legacyValue == "" {
// legacy key is not set, copy from new key
if err := rn.PipeE(yaml.SetAnnotation(legacyKey, value)); err != nil {
return err
}
}
return nil
}
func checkAnnotationsAltered(rn *yaml.RNode, meta yaml.ResourceMeta, nodeAnnosMap map[string]map[string]string) error {
// get the resource's current path, index, and ids from the new annotations
internal := nodeAnnotations{
path: meta.Annotations[kioutil.PathAnnotation],
index: meta.Annotations[kioutil.IndexAnnotation],
id: meta.Annotations[kioutil.IdAnnotation],
}
// get the resource's current path, index, and ids from the legacy annotations
legacy := nodeAnnotations{
path: meta.Annotations[kioutil.LegacyPathAnnotation],
index: meta.Annotations[kioutil.LegacyIndexAnnotation],
id: meta.Annotations[kioutil.LegacyIdAnnotation],
}
if internal.path == legacy.path &&
internal.index == legacy.index &&
internal.id == legacy.id {
// none of the annotations differ, so no reconciliation is needed
return nil
}
// nodeAnnosMap is a map of structure path -> index -> id that stores
// all of the resources' path/index/id annotations prior to the functions
// being run. We use that to check whether the legacy or new internal
// annotations have been changed, and make sure the change is reflected
// in the other.
// first, check if the internal annotations are found in nodeAnnosMap
if indexIdMap, ok := nodeAnnosMap[internal.path]; ok {
if id, ok := indexIdMap[internal.index]; ok {
if id == internal.id {
// the internal annotations of the resource match the ones stored in
// nodeAnnosMap, so we should copy the legacy annotations to the
// internal ones
if err := updateAnnotations(rn, meta,
[]string{
kioutil.PathAnnotation,
kioutil.IndexAnnotation,
kioutil.IdAnnotation,
},
[]string{
legacy.path,
legacy.index,
legacy.id,
}); err != nil {
return err
}
}
}
}
// check the opposite, to see if the legacy annotations are in nodeAnnosMap
if indexIdMap, ok := nodeAnnosMap[legacy.path]; ok {
if id, ok := indexIdMap[legacy.index]; ok {
if id == legacy.id {
// the legacy annotations of the resource match the ones stored in
// nodeAnnosMap, so we should copy the internal annotations to the
// legacy ones
if err := updateAnnotations(rn, meta,
[]string{
kioutil.LegacyPathAnnotation,
kioutil.LegacyIndexAnnotation,
kioutil.LegacyIdAnnotation,
},
[]string{
internal.path,
internal.index,
internal.id,
}); err != nil {
return err
}
}
}
}
return nil
}
func updateAnnotations(rn *yaml.RNode, meta yaml.ResourceMeta, keys []string, values []string) error {
if len(keys) != len(values) {
return fmt.Errorf("keys is not same length as values")
}
for i := range keys {
_, ok := meta.Annotations[keys[i]]
if values[i] == "" && !ok {
// don't set "" if annotation is not already there
continue
}
if err := rn.PipeE(yaml.SetAnnotation(keys[i], values[i])); err != nil {
return err
}
}
return nil
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
. "sigs.k8s.io/kustomize/kyaml/kio"
@@ -187,3 +187,347 @@ func TestContinueOnEmptyBehavior(t *testing.T) {
}
}
}
func TestLegacyAnnotationReconciliation(t *testing.T) {
noopFilter1 := func(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
return nodes, nil
}
noopFilter2 := func(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
return nodes, nil
}
changeInternalAnnos := func(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
for _, rn := range nodes {
if err := rn.PipeE(yaml.SetAnnotation(kioutil.PathAnnotation, "new")); err != nil {
return nil, err
}
if err := rn.PipeE(yaml.SetAnnotation(kioutil.IndexAnnotation, "new")); err != nil {
return nil, err
}
}
return nodes, nil
}
changeLegacyAnnos := func(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
for _, rn := range nodes {
if err := rn.PipeE(yaml.SetAnnotation(kioutil.LegacyPathAnnotation, "new")); err != nil {
return nil, err
}
if err := rn.PipeE(yaml.SetAnnotation(kioutil.LegacyIndexAnnotation, "new")); err != nil {
return nil, err
}
}
return nodes, nil
}
changeLegacyId := func(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
for _, rn := range nodes {
if err := rn.PipeE(yaml.SetAnnotation(kioutil.LegacyIdAnnotation, "new")); err != nil {
return nil, err
}
}
return nodes, nil
}
changeInternalId := func(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
for _, rn := range nodes {
if err := rn.PipeE(yaml.SetAnnotation(kioutil.IdAnnotation, "new")); err != nil {
return nil, err
}
}
return nodes, nil
}
noops := []Filter{
FilterFunc(noopFilter1),
FilterFunc(noopFilter2),
}
internal := []Filter{FilterFunc(changeInternalAnnos)}
legacy := []Filter{FilterFunc(changeLegacyAnnos)}
legacyId := []Filter{FilterFunc(changeLegacyId)}
internalId := []Filter{FilterFunc(changeInternalId)}
testCases := map[string]struct {
input string
filters []Filter
expected string
}{
// the orchestrator should copy the legacy annotations to the new
// annotations
"legacy annotations only": {
input: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
annotations:
config.kubernetes.io/path: 'configmap.yaml'
config.kubernetes.io/index: '0'
data:
grpcPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ports-to
annotations:
config.kubernetes.io/path: "configmap.yaml"
config.kubernetes.io/index: '1'
data:
grpcPort: 8081
`,
filters: noops,
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
annotations:
config.kubernetes.io/path: 'configmap.yaml'
config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'configmap.yaml'
internal.config.kubernetes.io/index: '0'
data:
grpcPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ports-to
annotations:
config.kubernetes.io/path: "configmap.yaml"
config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'configmap.yaml'
internal.config.kubernetes.io/index: '1'
data:
grpcPort: 8081
`,
},
// the orchestrator should copy the new annotations to the
// legacy annotations
"new annotations only": {
input: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
annotations:
internal.config.kubernetes.io/path: 'configmap.yaml'
internal.config.kubernetes.io/index: '0'
data:
grpcPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ports-to
annotations:
internal.config.kubernetes.io/path: "configmap.yaml"
internal.config.kubernetes.io/index: '1'
data:
grpcPort: 8081
`,
filters: noops,
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
annotations:
internal.config.kubernetes.io/path: 'configmap.yaml'
internal.config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'configmap.yaml'
config.kubernetes.io/index: '0'
data:
grpcPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ports-to
annotations:
internal.config.kubernetes.io/path: "configmap.yaml"
internal.config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'configmap.yaml'
config.kubernetes.io/index: '1'
data:
grpcPort: 8081
`,
},
// the orchestrator should detect that the legacy annotations
// have been changed by the function, and should update the
// new internal annotations to reflect the same change
"change only legacy annotations": {
input: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
annotations:
config.kubernetes.io/path: 'configmap.yaml'
config.kubernetes.io/index: '0'
data:
grpcPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ports-to
annotations:
config.kubernetes.io/path: "configmap.yaml"
config.kubernetes.io/index: '1'
data:
grpcPort: 8081
`,
filters: legacy,
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
annotations:
config.kubernetes.io/path: 'new'
config.kubernetes.io/index: 'new'
internal.config.kubernetes.io/path: 'new'
internal.config.kubernetes.io/index: 'new'
data:
grpcPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ports-to
annotations:
config.kubernetes.io/path: "new"
config.kubernetes.io/index: 'new'
internal.config.kubernetes.io/path: 'new'
internal.config.kubernetes.io/index: 'new'
data:
grpcPort: 8081
`,
},
// the orchestrator should detect that the new internal annotations
// have been changed by the function, and should update the
// legacy annotations to reflect the same change
"change only internal annotations": {
input: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
annotations:
config.kubernetes.io/path: 'configmap.yaml'
config.kubernetes.io/index: '0'
data:
grpcPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ports-to
annotations:
config.kubernetes.io/path: "configmap.yaml"
config.kubernetes.io/index: '1'
data:
grpcPort: 8081
`,
filters: internal,
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
annotations:
config.kubernetes.io/path: 'new'
config.kubernetes.io/index: 'new'
internal.config.kubernetes.io/path: 'new'
internal.config.kubernetes.io/index: 'new'
data:
grpcPort: 8080
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ports-to
annotations:
config.kubernetes.io/path: "new"
config.kubernetes.io/index: 'new'
internal.config.kubernetes.io/path: 'new'
internal.config.kubernetes.io/index: 'new'
data:
grpcPort: 8081
`,
},
// the orchestrator should detect that the new internal id annotation
// has been changed, and copy it over to the legacy one, and also
// copy the path and index legacy annotations to the new internal
// ones
"change only internal id when original legacy set": {
input: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
annotations:
config.kubernetes.io/path: 'configmap.yaml'
config.kubernetes.io/index: '0'
config.k8s.io/id: '1'
data:
grpcPort: 8080
`,
filters: internalId,
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
annotations:
config.kubernetes.io/path: 'configmap.yaml'
config.kubernetes.io/index: '0'
config.k8s.io/id: 'new'
internal.config.kubernetes.io/path: 'configmap.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/id: 'new'
data:
grpcPort: 8080
`,
},
// the orchestrator should detect that the legacy id annotation
// has been changed, and copy it over to the internal one, and also
// copy the path and index internal annotations to the legacy
// ones
"change only legacy id when internal legacy set": {
input: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
annotations:
internal.config.kubernetes.io/path: 'configmap.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/id: '1'
data:
grpcPort: 8080
`,
filters: legacyId,
expected: `apiVersion: v1
kind: ConfigMap
metadata:
name: ports-from
annotations:
internal.config.kubernetes.io/path: 'configmap.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/id: 'new'
config.kubernetes.io/path: 'configmap.yaml'
config.kubernetes.io/index: '0'
config.k8s.io/id: 'new'
data:
grpcPort: 8080
`,
},
}
for tn, tc := range testCases {
t.Run(tn, func(t *testing.T) {
var out bytes.Buffer
input := ByteReadWriter{
Reader: bytes.NewBufferString(tc.input),
Writer: &out,
OmitReaderAnnotations: true,
KeepReaderAnnotations: true,
}
p := Pipeline{
Inputs: []Reader{&input},
Filters: tc.filters,
Outputs: []Writer{&input},
}
assert.NoError(t, p.Execute())
assert.Equal(t, tc.expected, out.String())
})
}
}

View File

@@ -18,16 +18,31 @@ type AnnotationKey = string
const (
// IndexAnnotation records the index of a specific resource in a file or input stream.
IndexAnnotation AnnotationKey = "config.kubernetes.io/index"
IndexAnnotation AnnotationKey = "internal.config.kubernetes.io/index"
// PathAnnotation records the path to the file the Resource was read from
PathAnnotation AnnotationKey = "config.kubernetes.io/path"
PathAnnotation AnnotationKey = "internal.config.kubernetes.io/path"
// SeqIndentAnnotation records the sequence nodes indentation of the input resource
SeqIndentAnnotation AnnotationKey = "internal.config.kubernetes.io/seqindent"
// IdAnnotation records the id of the resource to map inputs to outputs
IdAnnotation = "internal.config.kubernetes.io/id"
// LegacyIndexAnnotation is the deprecated annotation key for resource index
LegacyIndexAnnotation AnnotationKey = "config.kubernetes.io/index"
// LegacyPathAnnotation is the deprecated annotation key for resource path
LegacyPathAnnotation AnnotationKey = "config.kubernetes.io/path"
// LegacyIdAnnotation is the deprecated annotation key for resource ids
LegacyIdAnnotation = "config.k8s.io/id"
)
func GetFileAnnotations(rn *yaml.RNode) (string, string, error) {
if err := CopyLegacyAnnotations(rn); err != nil {
return "", "", err
}
meta, err := rn.GetMeta()
if err != nil {
return "", "", err
@@ -37,6 +52,40 @@ func GetFileAnnotations(rn *yaml.RNode) (string, string, error) {
return path, index, nil
}
func CopyLegacyAnnotations(rn *yaml.RNode) error {
meta, err := rn.GetMeta()
if err != nil {
return err
}
if err := copyAnnotations(meta, rn, LegacyPathAnnotation, PathAnnotation); err != nil {
return err
}
if err := copyAnnotations(meta, rn, LegacyIndexAnnotation, IndexAnnotation); err != nil {
return err
}
if err := copyAnnotations(meta, rn, LegacyIdAnnotation, IdAnnotation); err != nil {
return err
}
return nil
}
func copyAnnotations(meta yaml.ResourceMeta, rn *yaml.RNode, legacyKey string, newKey string) error {
newValue := meta.Annotations[newKey]
if newValue != "" {
if err := rn.PipeE(yaml.SetAnnotation(legacyKey, newValue)); err != nil {
return err
}
} else {
legacyValue := meta.Annotations[legacyKey]
if legacyValue != "" {
if err := rn.PipeE(yaml.SetAnnotation(newKey, legacyValue)); err != nil {
return err
}
}
}
return nil
}
// ErrorIfMissingAnnotation validates the provided annotations are present on the given resources
func ErrorIfMissingAnnotation(nodes []*yaml.RNode, keys ...AnnotationKey) error {
for _, key := range keys {
@@ -67,6 +116,9 @@ func DefaultPathAndIndexAnnotation(dir string, nodes []*yaml.RNode) error {
// check each node for the path annotation
for i := range nodes {
if err := CopyLegacyAnnotations(nodes[i]); err != nil {
return err
}
m, err := nodes[i].GetMeta()
if err != nil {
return err
@@ -91,6 +143,9 @@ func DefaultPathAndIndexAnnotation(dir string, nodes []*yaml.RNode) error {
if err := nodes[i].PipeE(yaml.SetAnnotation(PathAnnotation, path)); err != nil {
return err
}
if err := nodes[i].PipeE(yaml.SetAnnotation(LegacyPathAnnotation, path)); err != nil {
return err
}
}
// set the index annotations
@@ -113,6 +168,10 @@ func DefaultPathAndIndexAnnotation(dir string, nodes []*yaml.RNode) error {
yaml.SetAnnotation(IndexAnnotation, fmt.Sprintf("%d", c))); err != nil {
return err
}
if err := nodes[i].PipeE(
yaml.SetAnnotation(LegacyIndexAnnotation, fmt.Sprintf("%d", c))); err != nil {
return err
}
}
return nil
}
@@ -122,6 +181,9 @@ func DefaultPathAndIndexAnnotation(dir string, nodes []*yaml.RNode) error {
func DefaultPathAnnotation(dir string, nodes []*yaml.RNode) error {
// check each node for the path annotation
for i := range nodes {
if err := CopyLegacyAnnotations(nodes[i]); err != nil {
return err
}
m, err := nodes[i].GetMeta()
if err != nil {
return err
@@ -137,6 +199,9 @@ func DefaultPathAnnotation(dir string, nodes []*yaml.RNode) error {
if err := nodes[i].PipeE(yaml.SetAnnotation(PathAnnotation, path)); err != nil {
return err
}
if err := nodes[i].PipeE(yaml.SetAnnotation(LegacyPathAnnotation, path)); err != nil {
return err
}
}
return nil
}
@@ -185,6 +250,12 @@ func SortNodes(nodes []*yaml.RNode) error {
if err != nil {
return false
}
if err := CopyLegacyAnnotations(nodes[i]); err != nil {
return false
}
if err := CopyLegacyAnnotations(nodes[j]); err != nil {
return false
}
var iMeta, jMeta yaml.ResourceMeta
if iMeta, _ = nodes[i].GetMeta(); err != nil {
return false

View File

@@ -98,6 +98,7 @@ metadata:
name: a
namespace: b
annotations:
internal.config.kubernetes.io/path: 'foo/b/bar_a.yaml'
config.kubernetes.io/path: 'foo/b/bar_a.yaml'
`, `with namespace`},
{
@@ -112,6 +113,7 @@ kind: Bar
metadata:
name: a
annotations:
internal.config.kubernetes.io/path: 'foo/bar_a.yaml'
config.kubernetes.io/path: 'foo/bar_a.yaml'
`, `without namespace`},
@@ -129,6 +131,7 @@ metadata:
name: a
namespace: b
annotations:
internal.config.kubernetes.io/path: 'b/bar_a.yaml'
config.kubernetes.io/path: 'b/bar_a.yaml'
`, `without dir`},
{
@@ -139,6 +142,7 @@ metadata:
name: a
namespace: b
annotations:
internal.config.kubernetes.io/path: 'a/b.yaml'
config.kubernetes.io/path: 'a/b.yaml'
`,
`apiVersion: v1
@@ -147,6 +151,7 @@ metadata:
name: a
namespace: b
annotations:
internal.config.kubernetes.io/path: 'a/b.yaml'
config.kubernetes.io/path: 'a/b.yaml'
`, `skip`},
}
@@ -184,7 +189,9 @@ metadata:
name: a
namespace: b
annotations:
internal.config.kubernetes.io/path: 'foo/b/bar_a.yaml'
config.kubernetes.io/path: 'foo/b/bar_a.yaml'
internal.config.kubernetes.io/index: '0'
config.kubernetes.io/index: '0'
`, `with namespace`},
{
@@ -199,7 +206,9 @@ kind: Bar
metadata:
name: a
annotations:
internal.config.kubernetes.io/path: 'foo/bar_a.yaml'
config.kubernetes.io/path: 'foo/bar_a.yaml'
internal.config.kubernetes.io/index: '0'
config.kubernetes.io/index: '0'
`, `without namespace`},
@@ -217,7 +226,9 @@ metadata:
name: a
namespace: b
annotations:
internal.config.kubernetes.io/path: 'b/bar_a.yaml'
config.kubernetes.io/path: 'b/bar_a.yaml'
internal.config.kubernetes.io/index: '0'
config.kubernetes.io/index: '0'
`, `without dir`},
{
@@ -228,7 +239,9 @@ metadata:
name: a
namespace: b
annotations:
internal.config.kubernetes.io/path: 'a/b.yaml'
config.kubernetes.io/path: 'a/b.yaml'
internal.config.kubernetes.io/index: '5'
config.kubernetes.io/index: '5'
`,
`apiVersion: v1
@@ -237,7 +250,9 @@ metadata:
name: a
namespace: b
annotations:
internal.config.kubernetes.io/path: 'a/b.yaml'
config.kubernetes.io/path: 'a/b.yaml'
internal.config.kubernetes.io/index: '5'
config.kubernetes.io/index: '5'
`, `skip`},
}

View File

@@ -82,6 +82,13 @@ type LocalPackageReadWriter struct {
// FileSystem can be used to mock the disk file system.
FileSystem filesys.FileSystemOrOnDisk
// WrapBareSeqNode wraps the bare sequence node document with map node,
// kyaml uses reader annotations to track resources, it is not possible to
// add them to bare sequence nodes, this option enables wrapping such bare
// sequence nodes into map node with key yaml.BareSeqNodeWrappingKey
// note that this wrapping is different and not related to ResourceList wrapping
WrapBareSeqNode bool
}
func (r *LocalPackageReadWriter) Read() ([]*yaml.RNode, error) {
@@ -95,6 +102,7 @@ func (r *LocalPackageReadWriter) Read() ([]*yaml.RNode, error) {
FileSkipFunc: r.FileSkipFunc,
PreserveSeqIndent: r.PreserveSeqIndent,
FileSystem: r.FileSystem,
WrapBareSeqNode: r.WrapBareSeqNode,
}.Read()
if err != nil {
return nil, errors.Wrap(err)
@@ -193,6 +201,13 @@ type LocalPackageReader struct {
// FileSystem can be used to mock the disk file system.
FileSystem filesys.FileSystemOrOnDisk
// WrapBareSeqNode wraps the bare sequence node document with map node,
// kyaml uses reader annotations to track resources, it is not possible to
// add them to bare sequence nodes, this option enables wrapping such bare
// sequence nodes into map node with key yaml.BareSeqNodeWrappingKey
// note that this wrapping is different and not related to ResourceList wrapping
WrapBareSeqNode bool
}
var _ Reader = LocalPackageReader{}
@@ -287,6 +302,7 @@ func (r *LocalPackageReader) readFile(path string, _ os.FileInfo) ([]*yaml.RNode
OmitReaderAnnotations: r.OmitReaderAnnotations,
SetAnnotations: r.SetAnnotations,
PreserveSeqIndent: r.PreserveSeqIndent,
WrapBareSeqNode: r.WrapBareSeqNode,
}
return rr.Read()
}
@@ -320,6 +336,7 @@ func (r *LocalPackageReader) initReaderAnnotations(path string, _ os.FileInfo) {
}
if !r.OmitReaderAnnotations {
r.SetAnnotations[kioutil.PathAnnotation] = path
r.SetAnnotations[kioutil.LegacyPathAnnotation] = path
}
}

View File

@@ -39,6 +39,13 @@ a: b #forth
metadata:
`)
var readFileE = []byte(`---
a: b #first
---
- foo # second
- bar
`)
var pkgFile = []byte(``)
func TestLocalPackageReader_Read_empty(t *testing.T) {
@@ -71,12 +78,16 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'a_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'a_test.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'a_test.yaml'
`,
`# second thing
e: f
@@ -88,18 +99,24 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'b_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'b_test.yaml'
`,
`a: b #third
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'c_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'c_test.yaml'
`,
`a: b #forth
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'd_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'd_test.yaml'
`,
}
for i := range nodes {
@@ -133,12 +150,16 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'a_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'a_test.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'a_test.yaml'
`,
`# second thing
e: f
@@ -150,12 +171,16 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'b_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'b_test.yaml'
`,
`a: b #third
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'c_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'c_test.yaml'
`,
}
for i := range nodes {
@@ -191,9 +216,9 @@ func TestLocalPackageReader_Read_JSON(t *testing.T) {
require.NoError(t, err)
require.Len(t, nodes, 2)
expected := []string{
`{"a": "b", metadata: {annotations: {config.kubernetes.io/index: '0', config.kubernetes.io/path: 'a_test.json'}}}
`{"a": "b", metadata: {annotations: {config.kubernetes.io/index: '0', config.kubernetes.io/path: 'a_test.json', internal.config.kubernetes.io/index: '0', internal.config.kubernetes.io/path: 'a_test.json'}}}
`,
`{"e": "f", "g": {"h": ["i", "j"]}, metadata: {annotations: {config.kubernetes.io/index: '0', config.kubernetes.io/path: 'b_test.json'}}}
`{"e": "f", "g": {"h": ["i", "j"]}, metadata: {annotations: {config.kubernetes.io/index: '0', config.kubernetes.io/path: 'b_test.json', internal.config.kubernetes.io/index: '0', internal.config.kubernetes.io/path: 'b_test.json'}}}
`,
}
for i := range nodes {
@@ -224,12 +249,16 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'a_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'a_test.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'a_test.yaml'
`,
}
for i := range nodes {
@@ -297,6 +326,8 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'a_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'a_test.yaml'
internal.config.kubernetes.io/seqindent: 'compact'
`,
`c: d # second
@@ -304,6 +335,8 @@ metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'a_test.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'a_test.yaml'
internal.config.kubernetes.io/seqindent: 'compact'
`,
`# second thing
@@ -316,6 +349,8 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'b_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'b_test.yaml'
internal.config.kubernetes.io/seqindent: 'compact'
`,
}
@@ -347,12 +382,16 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
`# second thing
e: f
@@ -364,6 +403,8 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'a${SEP}b${SEP}b_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'a${SEP}b${SEP}b_test.yaml'
`,
}
for i := range nodes {
@@ -397,12 +438,16 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
}
@@ -438,12 +483,16 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
}
@@ -480,12 +529,16 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
`c: d # second
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'a${SEP}b${SEP}a_test.yaml'
`,
`# second thing
e: f
@@ -497,6 +550,8 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'a${SEP}c${SEP}c_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'a${SEP}c${SEP}c_test.yaml'
`,
}
@@ -555,3 +610,45 @@ func testOnDiskAndOnMem(t *testing.T, files []mockFile, f func(t *testing.T, pat
f(t, "/", fs)
})
}
func TestLocalPackageReader_ReadBareSeqNodes(t *testing.T) {
testOnDiskAndOnMem(t, []mockFile{
{path: "a/b"},
{path: "a/c"},
{path: "e_test.yaml", content: readFileE},
}, func(t *testing.T, path string, mockFS filesys.FileSystem) {
rfr := LocalPackageReader{
PackagePath: path,
FileSystem: filesys.FileSystemOrOnDisk{FileSystem: mockFS},
WrapBareSeqNode: true,
}
nodes, err := rfr.Read()
require.NoError(t, err)
require.Len(t, nodes, 2)
expected := []string{
`a: b #first
metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'e_test.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'e_test.yaml'
`,
`bareSeqNodeWrappingKey:
- foo # second
- bar
metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'e_test.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'e_test.yaml'
`,
}
for i := range nodes {
val, err := nodes[i].String()
require.NoError(t, err)
require.Equal(t, expected[i], val)
}
})
}

View File

@@ -66,6 +66,7 @@ func (r LocalPackageWriter) Write(nodes []*yaml.RNode) error {
if !r.KeepReaderAnnotations {
r.ClearAnnotations = append(r.ClearAnnotations, kioutil.PathAnnotation)
r.ClearAnnotations = append(r.ClearAnnotations, kioutil.LegacyPathAnnotation)
}
// validate outputs before writing any

View File

@@ -73,14 +73,18 @@ func TestLocalPackageWriter_Write_keepReaderAnnotations(t *testing.T) {
require.Equal(t, `a: b #first
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/index: "0"
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/path: 'a/b/a_test.yaml'
internal.config.kubernetes.io/index: '0'
---
c: d # second
metadata:
annotations:
config.kubernetes.io/index: 1
config.kubernetes.io/index: "1"
config.kubernetes.io/path: "a/b/a_test.yaml"
internal.config.kubernetes.io/path: 'a/b/a_test.yaml'
internal.config.kubernetes.io/index: '1'
`, string(b))
b, err = fs.ReadFile(filepath.Join(d, "a", "b", "b_test.yaml"))
@@ -92,8 +96,10 @@ g:
- j
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/index: "0"
config.kubernetes.io/path: "a/b/b_test.yaml"
internal.config.kubernetes.io/path: 'a/b/b_test.yaml'
internal.config.kubernetes.io/index: '0'
`, string(b))
})
}
@@ -268,7 +274,7 @@ g:
metadata:
annotations:
config.kubernetes.io/path: a/
config.kubernetes.io/index: 0
config.kubernetes.io/index: "0"
`)
require.NoError(t, err)
@@ -294,14 +300,14 @@ func getWriterInputs(t *testing.T, mockFS filesys.FileSystem) (string, *yaml.RNo
node1, err := yaml.Parse(`a: b #first
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/index: "0"
config.kubernetes.io/path: "a/b/a_test.yaml"
`)
require.NoError(t, err)
node2, err := yaml.Parse(`c: d # second
metadata:
annotations:
config.kubernetes.io/index: 1
config.kubernetes.io/index: "1"
config.kubernetes.io/path: "a/b/a_test.yaml"
`)
require.NoError(t, err)
@@ -312,7 +318,7 @@ g:
- j
metadata:
annotations:
config.kubernetes.io/index: 0
config.kubernetes.io/index: "0"
config.kubernetes.io/path: "a/b/b_test.yaml"
`)
require.NoError(t, err)

View File

@@ -32,7 +32,7 @@ var GraphStructures = []string{string(TreeStructureGraph), string(TreeStructureP
// TreeWriter prints the package structured as a tree.
// TODO(pwittrock): test this package better. it is lower-risk since it is only
// used for printing rather than updating or editing.
// used for printing rather than updating or editing.
type TreeWriter struct {
Writer io.Writer
Root string
@@ -49,6 +49,11 @@ type TreeWriterField struct {
}
func (p TreeWriter) packageStructure(nodes []*yaml.RNode) error {
for i := range nodes {
if err := kioutil.CopyLegacyAnnotations(nodes[i]); err != nil {
return err
}
}
indexByPackage := p.index(nodes)
// create the new tree

View File

@@ -39,6 +39,12 @@ The above command will update the [OpenAPI schema] and the [Kustomization schema
create a directory kubernetesapi/v1212 and store the resulting
swagger.json and swagger.go files there.
#### Precomputations
To avoid expensive schema lookups, some functions have precomputed results based on the schema. Unit tests
ensure these are kept in sync with the schema; if these tests fail you will need to follow the suggested diff
to update the precomputed results.
### Run all tests
At the top of the repository, run the tests.

View File

@@ -50,6 +50,95 @@ type openapiData struct {
schemaInit bool
}
// precomputedIsNamespaceScoped precomputes IsNamespaceScoped for known types. This avoids Schema creation,
// which is expensive
// The test output from TestIsNamespaceScopedPrecompute shows the expected map in go syntax,and can be copy and pasted
// from the failure if it changes.
var precomputedIsNamespaceScoped = map[yaml.TypeMeta]bool{
{APIVersion: "admissionregistration.k8s.io/v1", Kind: "MutatingWebhookConfiguration"}: false,
{APIVersion: "admissionregistration.k8s.io/v1", Kind: "ValidatingWebhookConfiguration"}: false,
{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "MutatingWebhookConfiguration"}: false,
{APIVersion: "admissionregistration.k8s.io/v1beta1", Kind: "ValidatingWebhookConfiguration"}: false,
{APIVersion: "apiextensions.k8s.io/v1", Kind: "CustomResourceDefinition"}: false,
{APIVersion: "apiextensions.k8s.io/v1beta1", Kind: "CustomResourceDefinition"}: false,
{APIVersion: "apiregistration.k8s.io/v1", Kind: "APIService"}: false,
{APIVersion: "apiregistration.k8s.io/v1beta1", Kind: "APIService"}: false,
{APIVersion: "apps/v1", Kind: "ControllerRevision"}: true,
{APIVersion: "apps/v1", Kind: "DaemonSet"}: true,
{APIVersion: "apps/v1", Kind: "Deployment"}: true,
{APIVersion: "apps/v1", Kind: "ReplicaSet"}: true,
{APIVersion: "apps/v1", Kind: "StatefulSet"}: true,
{APIVersion: "autoscaling/v1", Kind: "HorizontalPodAutoscaler"}: true,
{APIVersion: "autoscaling/v1", Kind: "Scale"}: true,
{APIVersion: "autoscaling/v2beta1", Kind: "HorizontalPodAutoscaler"}: true,
{APIVersion: "autoscaling/v2beta2", Kind: "HorizontalPodAutoscaler"}: true,
{APIVersion: "batch/v1", Kind: "CronJob"}: true,
{APIVersion: "batch/v1", Kind: "Job"}: true,
{APIVersion: "batch/v1beta1", Kind: "CronJob"}: true,
{APIVersion: "certificates.k8s.io/v1", Kind: "CertificateSigningRequest"}: false,
{APIVersion: "certificates.k8s.io/v1beta1", Kind: "CertificateSigningRequest"}: false,
{APIVersion: "coordination.k8s.io/v1", Kind: "Lease"}: true,
{APIVersion: "coordination.k8s.io/v1beta1", Kind: "Lease"}: true,
{APIVersion: "discovery.k8s.io/v1", Kind: "EndpointSlice"}: true,
{APIVersion: "discovery.k8s.io/v1beta1", Kind: "EndpointSlice"}: true,
{APIVersion: "events.k8s.io/v1", Kind: "Event"}: true,
{APIVersion: "events.k8s.io/v1beta1", Kind: "Event"}: true,
{APIVersion: "extensions/v1beta1", Kind: "Ingress"}: true,
{APIVersion: "flowcontrol.apiserver.k8s.io/v1beta1", Kind: "FlowSchema"}: false,
{APIVersion: "flowcontrol.apiserver.k8s.io/v1beta1", Kind: "PriorityLevelConfiguration"}: false,
{APIVersion: "networking.k8s.io/v1", Kind: "Ingress"}: true,
{APIVersion: "networking.k8s.io/v1", Kind: "IngressClass"}: false,
{APIVersion: "networking.k8s.io/v1", Kind: "NetworkPolicy"}: true,
{APIVersion: "networking.k8s.io/v1beta1", Kind: "Ingress"}: true,
{APIVersion: "networking.k8s.io/v1beta1", Kind: "IngressClass"}: false,
{APIVersion: "node.k8s.io/v1", Kind: "RuntimeClass"}: false,
{APIVersion: "node.k8s.io/v1beta1", Kind: "RuntimeClass"}: false,
{APIVersion: "policy/v1", Kind: "PodDisruptionBudget"}: true,
{APIVersion: "policy/v1beta1", Kind: "PodDisruptionBudget"}: true,
{APIVersion: "policy/v1beta1", Kind: "PodSecurityPolicy"}: false,
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRole"}: false,
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "ClusterRoleBinding"}: false,
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "Role"}: true,
{APIVersion: "rbac.authorization.k8s.io/v1", Kind: "RoleBinding"}: true,
{APIVersion: "rbac.authorization.k8s.io/v1beta1", Kind: "ClusterRole"}: false,
{APIVersion: "rbac.authorization.k8s.io/v1beta1", Kind: "ClusterRoleBinding"}: false,
{APIVersion: "rbac.authorization.k8s.io/v1beta1", Kind: "Role"}: true,
{APIVersion: "rbac.authorization.k8s.io/v1beta1", Kind: "RoleBinding"}: true,
{APIVersion: "scheduling.k8s.io/v1", Kind: "PriorityClass"}: false,
{APIVersion: "scheduling.k8s.io/v1beta1", Kind: "PriorityClass"}: false,
{APIVersion: "storage.k8s.io/v1", Kind: "CSIDriver"}: false,
{APIVersion: "storage.k8s.io/v1", Kind: "CSINode"}: false,
{APIVersion: "storage.k8s.io/v1", Kind: "StorageClass"}: false,
{APIVersion: "storage.k8s.io/v1", Kind: "VolumeAttachment"}: false,
{APIVersion: "storage.k8s.io/v1beta1", Kind: "CSIDriver"}: false,
{APIVersion: "storage.k8s.io/v1beta1", Kind: "CSINode"}: false,
{APIVersion: "storage.k8s.io/v1beta1", Kind: "CSIStorageCapacity"}: true,
{APIVersion: "storage.k8s.io/v1beta1", Kind: "StorageClass"}: false,
{APIVersion: "storage.k8s.io/v1beta1", Kind: "VolumeAttachment"}: false,
{APIVersion: "v1", Kind: "ComponentStatus"}: false,
{APIVersion: "v1", Kind: "ConfigMap"}: true,
{APIVersion: "v1", Kind: "Endpoints"}: true,
{APIVersion: "v1", Kind: "Event"}: true,
{APIVersion: "v1", Kind: "LimitRange"}: true,
{APIVersion: "v1", Kind: "Namespace"}: false,
{APIVersion: "v1", Kind: "Node"}: false,
{APIVersion: "v1", Kind: "NodeProxyOptions"}: false,
{APIVersion: "v1", Kind: "PersistentVolume"}: false,
{APIVersion: "v1", Kind: "PersistentVolumeClaim"}: true,
{APIVersion: "v1", Kind: "Pod"}: true,
{APIVersion: "v1", Kind: "PodAttachOptions"}: true,
{APIVersion: "v1", Kind: "PodExecOptions"}: true,
{APIVersion: "v1", Kind: "PodPortForwardOptions"}: true,
{APIVersion: "v1", Kind: "PodProxyOptions"}: true,
{APIVersion: "v1", Kind: "PodTemplate"}: true,
{APIVersion: "v1", Kind: "ReplicationController"}: true,
{APIVersion: "v1", Kind: "ResourceQuota"}: true,
{APIVersion: "v1", Kind: "Secret"}: true,
{APIVersion: "v1", Kind: "Service"}: true,
{APIVersion: "v1", Kind: "ServiceAccount"}: true,
{APIVersion: "v1", Kind: "ServiceProxyOptions"}: true,
}
// ResourceSchema wraps the OpenAPI Schema.
type ResourceSchema struct {
// Schema is the OpenAPI schema for a Resource or field
@@ -207,15 +296,17 @@ func AddDefinitions(definitions spec.Definitions) {
}
// cast the extension to a []map[string]string
exts, ok := gvk.([]interface{})
if !ok || len(exts) != 1 {
continue
}
typeMeta, ok := toTypeMeta(exts[0])
if !ok {
continue
}
globalSchema.schemaByResourceType[typeMeta] = &d
for i := range exts {
typeMeta, ok := toTypeMeta(exts[i])
if !ok {
continue
}
globalSchema.schemaByResourceType[typeMeta] = &d
}
}
}
@@ -265,10 +356,17 @@ func GetSchema(s string, schema *spec.Schema) (*ResourceSchema, error) {
// cluster-scoped by looking at the information in the openapi schema.
// The second return value tells whether the provided type could be found
// in the openapi schema. If the value is false here, the scope of the
// resource is not known. If the type if found, the first return value will
// resource is not known. If the type is found, the first return value will
// be true if the resource is namespace-scoped, and false if the type is
// cluster-scoped.
func IsNamespaceScoped(typeMeta yaml.TypeMeta) (bool, bool) {
if res, f := precomputedIsNamespaceScoped[typeMeta]; f {
return res, true
}
return isNamespaceScopedFromSchema(typeMeta)
}
func isNamespaceScopedFromSchema(typeMeta yaml.TypeMeta) (bool, bool) {
initSchema()
isNamespaceScoped, found := globalSchema.namespaceabilityByResourceType[typeMeta]
return isNamespaceScoped, found

View File

@@ -8,6 +8,7 @@ import (
"io/ioutil"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
@@ -271,6 +272,14 @@ func TestIsNamespaceScoped_builtin(t *testing.T) {
}
}
// TestIsNamespaceScopedPrecompute checks that the precomputed result meets the actual result
func TestIsNamespaceScopedPrecompute(t *testing.T) {
initSchema()
if diff := cmp.Diff(globalSchema.namespaceabilityByResourceType, precomputedIsNamespaceScoped); diff != "" {
t.Fatalf(diff)
}
}
func TestIsNamespaceScoped_custom(t *testing.T) {
SuppressBuiltInSchemaUse()
err := AddSchema([]byte(`

View File

@@ -13,7 +13,9 @@ import (
// 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 {
// from node should not be modified, it should be just used as a reference
fromCopy := from.Copy()
if err := syncOrder(fromCopy, to); err != nil {
return errors.Errorf("failed to sync field order: %q", err.Error())
}
rearrangeHeadCommentOfSeqNode(to.YNode())

View File

@@ -5,6 +5,7 @@ package order
import (
"bytes"
"strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -413,6 +414,15 @@ status:
if !assert.Equal(t, tc.expected, out.String()) {
t.FailNow()
}
actualFrom, err := from.String()
if !assert.NoError(t, err) {
t.FailNow()
}
if !assert.Equal(t, strings.TrimSpace(tc.from), strings.TrimSpace(actualFrom)) {
t.FailNow()
}
})
}
}

View File

@@ -343,6 +343,12 @@ func sortFns(buff *kio.PackageBuffer) error {
// sort the nodes so that we traverse them depth first
// functions deeper in the file system tree should be run first
sort.Slice(buff.Nodes, func(i, j int) bool {
if err := kioutil.CopyLegacyAnnotations(buff.Nodes[i]); err != nil {
return false
}
if err := kioutil.CopyLegacyAnnotations(buff.Nodes[j]); err != nil {
return false
}
mi, _ := buff.Nodes[i].GetMeta()
pi := filepath.ToSlash(mi.Annotations[kioutil.PathAnnotation])
@@ -487,7 +493,12 @@ func (r *RunFns) ffp(spec runtimeutil.FunctionSpec, api *yaml.RNode, currentUser
var p string
if spec.Starlark.Path != "" {
p = filepath.ToSlash(path.Clean(m.Annotations[kioutil.PathAnnotation]))
pathAnno := m.Annotations[kioutil.PathAnnotation]
if pathAnno == "" {
pathAnno = m.Annotations[kioutil.LegacyPathAnnotation]
}
p = filepath.ToSlash(path.Clean(pathAnno))
spec.Starlark.Path = filepath.ToSlash(path.Clean(spec.Starlark.Path))
if filepath.IsAbs(spec.Starlark.Path) || path.IsAbs(spec.Starlark.Path) {
return nil, errors.Errorf(

View File

@@ -862,6 +862,8 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'cluster.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'cluster.yaml'
spec:
replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
`, `
@@ -872,6 +874,8 @@ metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'cluster.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'cluster.yaml'
spec:
replicas: 10
`},
@@ -883,6 +887,8 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'cluster.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'cluster.yaml'
spec:
replicas: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
`, `
@@ -893,6 +899,8 @@ metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'cluster.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'cluster.yaml'
spec:
replicas: 10
`},
@@ -917,6 +925,8 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'cluster.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'cluster.yaml'
spec:
replicas: 3 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
`, `
@@ -927,6 +937,8 @@ metadata:
annotations:
config.kubernetes.io/index: '1'
config.kubernetes.io/path: 'another_cluster.yaml'
internal.config.kubernetes.io/index: '1'
internal.config.kubernetes.io/path: 'another_cluster.yaml'
spec:
replicas: 10
`},
@@ -938,6 +950,8 @@ metadata:
annotations:
config.kubernetes.io/index: '0'
config.kubernetes.io/path: 'cluster.yaml'
internal.config.kubernetes.io/index: '0'
internal.config.kubernetes.io/path: 'cluster.yaml'
spec:
replicas: 4 # {"$ref": "#/definitions/io.k8s.cli.setters.replicas"}
`},

View File

@@ -14,6 +14,10 @@ const (
WideSequenceStyle SequenceIndentStyle = "wide"
CompactSequenceStyle SequenceIndentStyle = "compact"
DefaultIndent = 2
// BareSeqNodeWrappingKey kyaml uses reader annotations to track resources, it is not possible to
// add them to bare sequence nodes, this key is used to wrap such bare
// sequence nodes into map node, byteio_writer unwraps it while writing back
BareSeqNodeWrappingKey = "bareSeqNodeWrappingKey"
)
// SeqIndentType holds the indentation style for sequence nodes

View File

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

View File

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

View File

@@ -45,6 +45,7 @@ kind: ConfigMap
metadata:
annotations:
config.kubernetes.io/path: configmap_some-cm.yaml
internal.config.kubernetes.io/path: configmap_some-cm.yaml
modified-by: mixer-instance
name: some-cm
---
@@ -55,6 +56,7 @@ kind: ConfigMap
metadata:
annotations:
config.kubernetes.io/path: configmap_some-cm-copy.yaml
internal.config.kubernetes.io/path: configmap_some-cm-copy.yaml
name: some-cm-copy
---
apiVersion: v1
@@ -62,6 +64,7 @@ kind: ConfigMap
metadata:
annotations:
config.kubernetes.io/path: configmap_net-new.yaml
internal.config.kubernetes.io/path: configmap_net-new.yaml
name: net-new
`)
}

166
proposals/00-00-template.md Normal file
View File

@@ -0,0 +1,166 @@
<!--
**Note:** When your proposal is complete, all of these comment blocks should be removed.
To get started with this template:
- [ ] **Make a copy of this file.**
Name it `YY-MM-short-descriptive-title.md` (where `YY-MM` is the current year and month).
- [ ] **Fill out this file as best you can.**
At minimum, you should fill in the "Summary" and "Motivation" sections.
- [ ] **Create a PR.**
Ping `@kubernetes-sigs/kustomize-admins` and `@kubernetes-sigs/kustomize-maintainers`.
-->
# Your short, descriptive title
**Authors**: <!-- Your github handle -->
**Reviewers**: <!-- List at least one Kustomize approver (https://github.com/kubernetes-sigs/kustomize/blob/master/OWNERS#L2) -->
**Status**: implementable
<!--
In general, all proposals made should be merged for the record, whether or not they are accepted.
Use the status field to record the results of the latest review:
- implementable: The default for this repo. If the proposal is merged, you can start working on it.
- deferred: The proposal may be accepted in the future, but it has been shelved for the time being.
A new PR must be opened to update the proposal and gain reviewer consensus before work can begin.
- withdrawn: The author changed their mind and no longer wants to pursue the proposal.
A new PR must be opened to update the proposal and gain reviewer consensus before work can begin.
- rejected: This proposal should not be implemented.
- replaced: If you submit a new proposal that supersedes an older one,
update the older one's status to "replaced by <link>".
-->
## Summary
<!--
In one short paragraph, summarize why this change is important to Kustomize users.
-->
## Motivation
<!--
If this proposal is an expansion of an existing GitHub issue, link to it here.
-->
**Goals:**
<!--
List the specific goals of the proposal. What is it trying to achieve? How will we
know that this has succeeded?
-->
1. A goal
1. Another goal
**Non-goals:**
<!--
What is out of scope for this proposal? Listing non-goals helps to focus discussion
and make progress.
-->
1. A non-goal
1. Another non-goal
## Proposal
<!--
This is where we get down to the specifics of what the proposal actually is.
Include enough information to illustrate your proposal, but try not to
overwhelm reviewers with details. Focus on APIs and interfaces rather than implementation details,
e.g.:
- Does this proposal require new kinds, fields or CLI flags?
- Will this feature require extending the public interface of Kustomize's Go packages?
(it's ok if you're not sure yet)
A proof of concept PR is NOT required but is preferable to including large amounts of code
inline here, if you feel such implementation details are required to adequately explain your design.
If you have a PR, link to it at the top of this section.
-->
### User Stories
<!--
Describe what people will be able to do if this KEP is implemented. If different user personas
will use the feature differently, consider writing separate stories for each.
Include as much detail as possible so that people can understand the "how" of the system.
The goal here is to make this feel real for users without getting bogged down.
-->
#### Story 1
Scenario summary: As a [end user, extension developer, ...], I want to [...]
<!--
A walkthrough of what it will look like for a user to take advantage of the new feature.
Include the the steps the user will take and samples of the commands they'll run
and config they'll use.
-->
#### Story 2
Scenario summary: As a [end user, extension developer, ...], I want to [...]
<!--
A walkthrough of what it will look like for a user to take advantage of the new feature.
Include the the steps the user will take and samples of the commands they'll run
and config they'll use.
-->
### Risks and Mitigations
<!--
What are the risks of this proposal, and how do we mitigate? Think broadly.
For example, consider both security, end-user privacy, and how this will
impact the larger Kubernetes ecosystem.
-->
### Dependencies
<!--
Kustomize tightly controls its Go dependencies in order to remain approved for
integration into kubectl. It cannot depend directly on kubectl or apimachinery code.
Identify any new Go dependencies this proposal will require Kustomize to pull in.
If any of them are large, is there another option?
-->
### Scalability
<!--
Is this feature expected to have a performance impact?
Explain to what extent and under what conditions.
-->
## Drawbacks
<!--
Why should this proposal _not_ be implemented?
-->
## Alternatives
<!--
What other approaches did you consider, and why did you rule them out? Be concise,
but do include enough information to express the idea and why it was not acceptable.
-->
## Rollout Plan
<!--
Depending on the scope of the features and the risks enabling it implies,
you may need to use a formal graduation process. If you don't think this is
necessary, explain why here, and delete the alpha/beta/GA headings below.
-->
### Alpha
<!--
New Kinds should be introduced with an alpha group version.
New major features should often be gated by an alpha flag at first.
New transformers can be introduced for use in the generators/validators/transformers fields
before they get their own top-level field in Kustomization.
-->
- Will the feature be gated by an "alpha" flag? Which one?
- Will the feature be available in `kubectl kustomize` during alpha? Why or why not?
### Beta
<!--
If the alpha was not available in `kubectl kustomize`, you need a beta phase where it is.
Full parity with `kubectl kustomize` is required at this stage.
-->
### GA
<!--
You should generally wait at least two `kubectl` release cycles before promotion to GA,
to ensure that the broader user base has time to try the feature and provide feedback.
For example, if your feature first appears in kubectl 1.23, promote it in 1.25 or later.
-->

48
proposals/README.md Normal file
View File

@@ -0,0 +1,48 @@
# Kustomize Enhancement Proposal Processes
So you want to propose an enhancement to Kustomize—awesome! Choose the option below that best fits the scope of your idea. Before you get started, it's a good idea to review the list of [eschewed features](https://kubectl.docs.kubernetes.io/faq/kustomize/eschewedfeatures).
[SIG CLI]: https://github.com/kubernetes/community/tree/master/sig-cli
[Enhancements repo]: https://github.com/kubernetes/enhancements
### Option 1: Github issue
Small, straightforward enhancements can be proposed in regular GitHub issues. As a rule of thumb, the enhancement should be resolvable in a single PR that is at most size L.
**Example features**:
- a new Kustomization field that does something very straightforward, like annotating resources
- a new option for an existing built-in transformer
**Instructions**: [Open an issue](https://github.com/kubernetes-sigs/kustomize/issues/new?labels=kind%2Ffeature&template=feature_request.md)
### Option 2: Mini (In-Repo) Enhancement Proposal
If your feature may be controversial or has a lot of details to explain, you should write it up as a mini enhancement proposal on this repo. This process is still relatively lightweight, but allows for more in-depth discussion of multiple details. Because it is submitted as a PR, reviewers can comment on individual lines of the proposal, facilitating discussion. The PR will be merged whether the feature is accepted or rejected, creating a record of the decisions.
Since the proposal template is a subset of what's required for the full KEP process, this can also be a good option if you're unsure whether your proposal is of general interest to [SIG-CLI]. You can use it to get preliminary feedback from the Kustomize team before bringing a full-fledged KEP to the SIG.
**Example features**:
- a new built-in transformer with several configurable options
- a feature that will bring in a significant new dependency
- a feature that introduces a new class of behavior, such as manipulating data within an opaque resource field
- a new build process that does not affect `kubectl kustomize`
**Instructions**:
1. Make a copy of [00-00-template.md](00-00-template.md) and rename it with the current date (e.g. 21-08) and a succinct title.
1. Fill out the template.
1. Submit it for review as a PR.
1. (Optional) Present your proposal at a biweekly [SIG-CLI] meeting. This can be a good way to get more traction for your proposal. Your presentation should be a quick summary to help folks understand whether the proposal is relevant to them.
### Option 3: Kubernetes Enhancement Proposal (KEP)
If your feature changes behaviour in a way that has significance for `kubectl kustomize`, particularly in terms of security, you will need to follow the full KEP process and get buy-in from [SIG-CLI] leadership in addition to Kustomize maintainers. Note that you can still submit a mini (in-repo) enhancement proposal as a first step to get preliminary feedback, including on whether a full KEP is required.
**Example features**:
- a feature with significant privacy or security implications to work out
- a feature that is not purely localized to the Kustomize binary on the end user's machine (e.g. downloads something remote, executes something external)
**Instructions**:
1. Follow the process on the [Enhancements repo]. Be sure to put your KEP in the directory for SIG-CLI.
1. (Strongly recommended) Send a link to your KEP to the [SIG-CLI] mailing list.
1. (Strongly recommended) Present your KEP at a biweekly [SIG-CLI] meeting. Your presentation should be a quick summary to help folks understand whether the KEP is relevant to them.
1. After your KEP is accepted, remember to update its metadata as your feature proceeds through the release process.

View File

@@ -15,6 +15,20 @@
This document describes how to perform a [semver release]
of one of the several [Go modules] in this repository.
#### semver review
Go's [semver]-compatible version tags take the form `v{major}.{minor}.{patch}`:
| major | minor | patch |
| :---: | :---: | :---: |
| API change | enhancements | bug fixes |
| manual update | maybe auto-update | auto-update encouraged |
- If there are only bug fixes or refactors, increment `patch` from whatever it is now.
- If there are new features, increment `minor`.
- If there's an API change (either the Go API or the CLI behavior
with respect to CLI arguments and flags), increment `major`.
## Release sequence
The dependencies determine the release order:
@@ -28,10 +42,17 @@ The dependencies determine the release order:
Thus, do `kyaml` first, then `cmd/config`, etc.
## Prep work
#### Prepare your source directory
The release scripts expect Kustomize code to be cloned at a path ending in `sigs.k8s.io/kustomize`. Run all commands from that directory unless otherwise specified.
#### Consider fetching new OpenAPI data
The Kubernetes OpenAPI data changes no more frequently than once per quarter.
You can check the current builtin versions that kustomize is using with the
following command.
```
kustomize openapi info
```
@@ -39,46 +60,10 @@ kustomize openapi info
Instructions on how to get a new OpenAPI sample can be found in the
[OpenAPI Readme].
## Prep work
#### Make some helper functions
#### Load some helper functions
```
function createBranch {
branch=$1
echo "Making branch $branch : \"$title\""
git branch -D $branch # delete if it exists
git checkout -b $branch
git commit -a -m "$title"
git push -f origin $branch
}
```
```
function createPr {
gh pr create --title "$title" --body "ALLOW_MODULE_SPAN" --base master
}
```
```
function refreshMaster {
git checkout master
git fetch upstream
git rebase upstream/master
}
```
```
function testKustomizeRepo {
make prow-presubmit-check >& /tmp/k.txt
local code=$?
if [ $code -ne 0 ]; then
echo "**** FAILURE ******************"
tail /tmp/k.txt
else
echo "LGTM"
fi
}
source releasing/helpers.sh
```
#### Install the release tool
@@ -101,18 +86,21 @@ echo $GITHUB_TOKEN | gh auth login --scopes repo --with-token
#### Establish clean state
```
cd ~/gopath/src/sigs.k8s.io/kustomize
refreshMaster
testKustomizeRepo
```
While you're waiting for the tests, review the commit log. Based on the changes to be included in this release, decide whether a patch, minor or major version bump is needed: [semver review].
kyaml has no intra-repo deps, so if the tests pass,
it can just be released.
Release it:
#### Release it
The default increment is a new patch version.
```
gorepomod release kyaml --doIt
gorepomod release kyaml [patch|minor|major] --doIt
```
Note the version:
@@ -120,26 +108,27 @@ Note the version:
versionKyaml=v0.10.20 # EDIT THIS!
```
Undraft the release on the [kustomize repo release page],
make sure the version number is what you expect.
Undraft the release on the [kustomize repo release page]:
* Make sure the version number is what you expect.
* Remove references to commits that aren't relevant to end users of this module (e.g. test commits, refactors).
* Make sure each commit left in the release notes includes a PR reference.
## Release `cmd/config`
```
cd ../kustomize
```
Pin to the most recent kyaml.
#### Pin to the most recent kyaml
```
gorepomod pin kyaml --doIt
go mod edit -require=sigs.k8s.io/kustomize/kyaml@$versionKyaml plugin/builtin/prefixsuffixtransformer/go.mod
go mod edit -require=sigs.k8s.io/kustomize/kyaml@$versionKyaml plugin/builtin/replicacounttransformer/go.mod
```
Create the PR:
```
title="Pin to kyaml $versionKyaml"
createBranch pinToKyaml
createBranch pinToKyaml $title
createPr
```
@@ -160,9 +149,12 @@ refreshMaster
testKustomizeRepo
```
Release it:
While you're waiting for the tests, review the commit log. Based on the changes to be included in this release, decide whether a patch, minor or major version bump is needed: [semver review].
#### Release it
```
gorepomod release cmd/config --doIt
gorepomod release cmd/config [patch|minor|major] --doIt
```
Note the version:
@@ -170,8 +162,10 @@ Note the version:
versionCmdConfig=v0.9.12 # EDIT THIS!
```
Undraft the release on the [kustomize repo release page],
make sure the version number is what you expect.
Undraft the release on the [kustomize repo release page]:
* Make sure the version number is what you expect.
* Remove references to commits that aren't relevant to end users of this module (e.g. test commits, refactors).
* Make sure each commit left in the release notes includes a PR reference.
## Release `api`
@@ -179,7 +173,7 @@ make sure the version number is what you expect.
This is the kustomize API, used by the kustomize CLI.
Pin to the new cmd/config:
#### Pin to the new cmd/config
```
gorepomod pin cmd/config --doIt
@@ -188,7 +182,7 @@ gorepomod pin cmd/config --doIt
Create the PR:
```
title="Pin to cmd/config $versionCmdConfig"
createBranch pinToCmdConfig
createBranch pinToCmdConfig $title
createPr
```
@@ -209,9 +203,12 @@ refreshMaster
testKustomizeRepo
```
Release it:
While you're waiting for the tests, review the commit log. Based on the changes to be included in this release, decide whether a patch, minor or major version bump is needed: [semver review].
#### Release it
```
gorepomod release api --doIt
gorepomod release api [patch|minor|major] --doIt
```
Note the version:
@@ -219,13 +216,16 @@ Note the version:
versionApi=v0.8.10 # EDIT THIS!
```
Undraft the release on the [kustomize repo release page],
make sure the version number is what you expect.
Undraft the release on the [kustomize repo release page]:
* Make sure the version number is what you expect.
* Remove references to commits that aren't relevant to end users of this module (e.g. test commits, refactors).
* Make sure each commit left in the release notes includes a PR reference.
## Release the kustomize CLI
Pin to the new API:
#### Pin to the new API
```
gorepomod pin api --doIt
```
@@ -233,7 +233,7 @@ gorepomod pin api --doIt
Create the PR:
```
title="Pin to api $versionApi"
createBranch pinToApi
createBranch pinToApi $title
createPr
```
@@ -254,12 +254,18 @@ refreshMaster
testKustomizeRepo
```
Release it:
While you're waiting for the tests, review the commit log. Based on the changes to be included in this release, decide whether a patch, minor or major version bump is needed: [semver review].
#### Release it
```
gorepomod release kustomize --doIt
gorepomod release kustomize [patch|minor|major] --doIt
```
Undraft the release on the [kustomize repo release page].
Undraft the release on the [kustomize repo release page]:
* Make sure the version number is what you expect.
* Remove references to commits that aren't relevant to end users of the CLI (e.g. test commits, refactors, changes that only surface in Go).
* Make sure each commit left in the release notes includes a PR reference.
## Confirm the kustomize binary is correct
@@ -308,10 +314,31 @@ refreshMaster
testKustomizeRepo
```
### Publish Official Docker Image
## Update example test target
[Makefile]: https://github.com/kubernetes-sigs/kustomize/blob/master/Makefile
Edit the `prow-presubmit-target` in the [Makefile]
to test examples against your new release.
```
sed -i "" "s/LATEST_V4_RELEASE=.*/LATEST_V4_RELEASE=v4.3.0/" Makefile
title="Test examples against latest release"
createBranch updateProwExamplesTarget $title
createPr
```
Wait for tests to pass, then merge the PR:
```
gh pr status # rinse, repeat
gh pr merge -m
```
## Publish Official Docker Image
[k8s.io]: https://github.com/kubernetes/k8s.io
[k8s-staging-kustomize]: https://pantheon.corp.google.com/gcr/images/k8s-staging-kustomize?project=k8s-staging-kustomize
[k8s-staging-kustomize]: https://console.cloud.google.com/gcr/images/k8s-staging-kustomize?project=k8s-staging-kustomize
Fork and clone the [k8s.io] repo.
@@ -326,13 +353,6 @@ project [k8s-staging-kustomize].
Commit and push your changes. Then create a PR to [k8s.io] to promote
new images. Assign the PR to @monopole and @Shell32-natsu.
### Finally
[Makefile]: https://github.com/kubernetes-sigs/kustomize/blob/master/Makefile
Edit the `prow-presubmit-target` in the [Makefile]
to test examples against your new release.
----
----
@@ -462,21 +482,6 @@ Set the version you want:
major=0; minor=1; patch=0
```
#### semver review
Go's [semver]-compatible version tags take the form `v{major}.{minor}.{patch}`:
| major | minor | patch |
| :---: | :---: | :---: |
| API change | enhancements | bug fixes |
| manual update | maybe auto-update | auto-update encouraged |
- If there are only bug fixes or refactors, increment `patch` from whatever it is now.
- If there are new features, increment `minor`.
- If there's an API change (either the Go API or the CLI behavior
with respect to CLI arguments and flags), increment `major`.
### Create the release branch

32
releasing/helpers.sh Normal file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env bash
function createBranch {
branch=$1
title=$2
echo "Making branch $branch : \"$title\""
git branch -D $branch # delete if it exists
git checkout -b $branch
git commit -a -m "$title"
git push -f origin $branch
}
function createPr {
gh pr create --title "$title" --body "ALLOW_MODULE_SPAN" --base master
}
function refreshMaster {
git checkout master
git fetch upstream
git rebase upstream/master
}
function testKustomizeRepo {
make prow-presubmit-check >& /tmp/k.txt
local code=$?
if [ $code -ne 0 ]; then
echo "**** FAILURE ******************"
tail /tmp/k.txt
else
echo "LGTM"
fi
}