mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-20 21:38:17 +00:00
Compare commits
1 Commits
release-ap
...
kustomize/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ba90fe5ef |
@@ -1,8 +0,0 @@
|
||||
.github
|
||||
docs
|
||||
examples
|
||||
functions
|
||||
hack
|
||||
site
|
||||
travis
|
||||
*.md
|
||||
68
.github/ISSUE_TEMPLATE/bug_report.md
vendored
68
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,68 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ""
|
||||
labels:
|
||||
- kind/bug
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
<!--
|
||||
Please read this page: https://kubernetes-sigs.github.io/kustomize/contributing/bugs/ before
|
||||
filing a bug
|
||||
-->
|
||||
|
||||
<!-- Feel free to skip the sections if they are not applicable. -->
|
||||
|
||||
**Describe the bug**
|
||||
|
||||
<!-- A clear and concise description of what the bug is. -->
|
||||
|
||||
**Files that can reproduce the issue**
|
||||
|
||||
<!--
|
||||
We cannot figure out or fix the issue if we don't know how to reproduce. Please
|
||||
provide a minimum set of files that can reproduce the issue. You can paste the
|
||||
file contents here or provide a link to a tarball or git repo.
|
||||
|
||||
Example:
|
||||
|
||||
kustomization.yaml
|
||||
|
||||
```
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
...
|
||||
```
|
||||
|
||||
resources.yaml
|
||||
|
||||
```
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
...
|
||||
```
|
||||
|
||||
...
|
||||
-->
|
||||
|
||||
**Expected output**
|
||||
|
||||
<!-- What's the expected output? -->
|
||||
|
||||
**Actual output**
|
||||
|
||||
<!-- What's the actual output? -->
|
||||
|
||||
**Kustomize version**
|
||||
|
||||
<!-- Please use the latest version when it's possible. -->
|
||||
|
||||
**Platform**
|
||||
|
||||
<!-- Linux/macOS/Windows? -->
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context about the problem here. -->
|
||||
1
.github/ISSUE_TEMPLATE/config.yaml
vendored
1
.github/ISSUE_TEMPLATE/config.yaml
vendored
@@ -1 +0,0 @@
|
||||
blank_issues_enabled: true
|
||||
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
26
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ""
|
||||
labels:
|
||||
- kind/feature
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
<!-- Feel free to skip the sections if they are not applicable. -->
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
|
||||
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
9
.github/ISSUE_TEMPLATE/question.md
vendored
9
.github/ISSUE_TEMPLATE/question.md
vendored
@@ -1,9 +0,0 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about the kustomize
|
||||
title: "[Question]"
|
||||
labels: ""
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
<!-- Please describe your question here -->
|
||||
113
.github/workflows/go.yml
vendored
113
.github/workflows/go.yml
vendored
@@ -1,113 +0,0 @@
|
||||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: [ubuntu-latest]
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Lint
|
||||
run: ./hack/kyaml-pre-commit.sh
|
||||
env:
|
||||
KUSTOMIZE_DOCKER_E2E: false # don't need to do e2e tests for linting
|
||||
|
||||
test-linux:
|
||||
name: Test Linux
|
||||
runs-on: [ubuntu-latest]
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Test kyaml
|
||||
run: go test -cover ./...
|
||||
working-directory: ./kyaml
|
||||
|
||||
- name: Test api
|
||||
run: go test -cover ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"
|
||||
working-directory: ./api
|
||||
|
||||
- name: Test cmd/config
|
||||
run: go test -cover ./...
|
||||
working-directory: ./cmd/config
|
||||
env:
|
||||
KUSTOMIZE_DOCKER_E2E: true
|
||||
|
||||
test-macos:
|
||||
name: Test MacOS
|
||||
runs-on: [macos-latest]
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Test kyaml
|
||||
run: go test -cover ./...
|
||||
working-directory: ./kyaml
|
||||
|
||||
- name: Test api
|
||||
run: go test -cover ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"
|
||||
working-directory: ./api
|
||||
|
||||
- name: Test cmd/config
|
||||
run: go test -cover ./...
|
||||
working-directory: ./cmd/config
|
||||
env:
|
||||
KUSTOMIZE_DOCKER_E2E: false # docker not installed on mac
|
||||
|
||||
test-windows:
|
||||
name: Test Windows
|
||||
runs-on: [windows-latest]
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.16
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Test kyaml
|
||||
run: go test -cover ./...
|
||||
working-directory: ./kyaml
|
||||
|
||||
# TODO: uncomment once Windows tests are passing.
|
||||
# - name: Test api
|
||||
# run: go test -cover ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"
|
||||
# working-directory: ./api
|
||||
|
||||
- name: Test cmd/config
|
||||
run: go test -cover ./...
|
||||
working-directory: ./cmd/config
|
||||
env:
|
||||
KUSTOMIZE_DOCKER_E2E: false # docker on windows not working well yet
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -19,5 +19,3 @@
|
||||
|
||||
# macOS
|
||||
*.DS_store
|
||||
|
||||
.bin
|
||||
|
||||
@@ -7,13 +7,8 @@ linters:
|
||||
- bodyclose
|
||||
- deadcode
|
||||
- depguard
|
||||
# - dogsled
|
||||
- dupl
|
||||
# - errcheck
|
||||
# - funlen
|
||||
# - gochecknoinits
|
||||
- goconst
|
||||
# - gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
@@ -26,7 +21,6 @@ linters:
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
# - scopelint
|
||||
- staticcheck
|
||||
- structcheck
|
||||
# stylecheck demands that acronyms not be treated as words
|
||||
@@ -34,10 +28,9 @@ linters:
|
||||
# - stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
- unparam
|
||||
- varcheck
|
||||
# - whitespace
|
||||
|
||||
linters-settings:
|
||||
dupl:
|
||||
|
||||
42
.travis.yml
Normal file
42
.travis.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
# TODO: Speed up the slowness of the osx travis runs
|
||||
# Maybe cache brew installs?
|
||||
#
|
||||
# TODO: Uncomment when some gets the tests running on Windows.
|
||||
# - windows
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- tree
|
||||
homebrew:
|
||||
packages:
|
||||
- tree
|
||||
update: true
|
||||
|
||||
# Only clone the most recent commit.
|
||||
git:
|
||||
depth: 1
|
||||
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.13"
|
||||
|
||||
go_import_path: sigs.k8s.io/kustomize
|
||||
|
||||
before_install:
|
||||
- source ./travis/consider-early-travis-exit.sh
|
||||
|
||||
# Skip the install process; let pre-commit.sh do it.
|
||||
install: true
|
||||
|
||||
script:
|
||||
- make verify-kustomize
|
||||
- ./travis/kyaml-pre-commit.sh
|
||||
|
||||
# TBD. Suppressing for now.
|
||||
notifications:
|
||||
email: false
|
||||
@@ -1,25 +1,6 @@
|
||||
[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]. 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](code-of-conduct.md). 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._
|
||||
|
||||
@@ -27,45 +8,19 @@ _As contributors and maintainers of this project, and in the interest of fosteri
|
||||
|
||||
Dev guides:
|
||||
|
||||
- [Contribution Guide]
|
||||
- [MacOS Dev Guide]
|
||||
- [Windows Dev Guide]
|
||||
- [Mac](docs/macDevGuide.md)
|
||||
|
||||
General resources for contributors:
|
||||
We have full documentation on how to get started contributing here:
|
||||
|
||||
- [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].
|
||||
- [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
|
||||
|
||||
## Mentorship
|
||||
|
||||
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
|
||||
|
||||
## Contributor Ladder
|
||||
|
||||
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. 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:
|
||||
|
||||
- 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]
|
||||
- [Mailing list]
|
||||
- [Slack channel](https://kubernetes.slack.com/messages/sig-cli)
|
||||
- [Mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-cli)
|
||||
|
||||
264
Makefile
264
Makefile
@@ -3,93 +3,60 @@
|
||||
#
|
||||
# Makefile for kustomize CLI and API.
|
||||
|
||||
SHELL := /usr/bin/env bash
|
||||
GOOS = $(shell go env GOOS)
|
||||
GOARCH = $(shell go env GOARCH)
|
||||
MYGOBIN = $(shell go env GOBIN)
|
||||
ifeq ($(MYGOBIN),)
|
||||
MYGOBIN = $(shell go env GOPATH)/bin
|
||||
endif
|
||||
MYGOBIN := $(shell go env GOPATH)/bin
|
||||
SHELL := /bin/bash
|
||||
export PATH := $(MYGOBIN):$(PATH)
|
||||
MODULES := '"cmd/config" "api/" "kustomize/" "kyaml/"'
|
||||
LATEST_V4_RELEASE=v4.4.0
|
||||
|
||||
# Provide defaults for REPO_OWNER and REPO_NAME if not present.
|
||||
# Typically these values would be provided by Prow.
|
||||
ifndef REPO_OWNER
|
||||
REPO_OWNER := "kubernetes-sigs"
|
||||
endif
|
||||
|
||||
ifndef REPO_NAME
|
||||
REPO_NAME := "kustomize"
|
||||
endif
|
||||
|
||||
.PHONY: all
|
||||
all: install-tools verify-kustomize
|
||||
all: verify-kustomize
|
||||
|
||||
.PHONY: verify-kustomize
|
||||
verify-kustomize: \
|
||||
lint-kustomize \
|
||||
test-unit-kustomize-all \
|
||||
test-examples-kustomize-against-HEAD \
|
||||
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
|
||||
.PHONY: prow-presubmit-check
|
||||
prow-presubmit-check: \
|
||||
install-tools \
|
||||
lint-kustomize \
|
||||
test-multi-module \
|
||||
test-unit-kustomize-all \
|
||||
test-unit-cmd-all \
|
||||
test-go-mod \
|
||||
test-examples-kustomize-against-HEAD \
|
||||
test-examples-kustomize-against-v4-release
|
||||
|
||||
.PHONY: verify-kustomize-e2e
|
||||
verify-kustomize-e2e: test-examples-e2e-kustomize
|
||||
test-examples-kustomize-against-latest
|
||||
|
||||
# Other builds in this repo might want a different linter version.
|
||||
# Without one Makefile to rule them all, the different makes
|
||||
# cannot assume that golanci-lint is at the version they want.
|
||||
# This installs what kustomize wants to use.
|
||||
# cannot assume that golanci-lint is at the version they want
|
||||
# since everything uses the same implicit GOPATH.
|
||||
# This installs in a temp dir to avoid overwriting someone else's
|
||||
# linter, then installs in MYGOBIN with a new name.
|
||||
# Version pinned by api/go.mod
|
||||
$(MYGOBIN)/golangci-lint-kustomize:
|
||||
rm -f $(CURDIR)/hack/golangci-lint
|
||||
GOBIN=$(CURDIR)/hack go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.23.8
|
||||
mv $(CURDIR)/hack/golangci-lint $(MYGOBIN)/golangci-lint-kustomize
|
||||
( \
|
||||
set -e; \
|
||||
export GOBIN=$$(mktemp -d) \
|
||||
cd api; \
|
||||
GO111MODULE=on go install github.com/golangci/golangci-lint/cmd/golangci-lint; \
|
||||
mv $$GOBIN/golangci-lint $(MYGOBIN)/golangci-lint-kustomize \
|
||||
)
|
||||
|
||||
# Version pinned by api/go.mod
|
||||
$(MYGOBIN)/mdrip:
|
||||
go install github.com/monopole/mdrip@v1.0.2
|
||||
cd api; \
|
||||
go install github.com/monopole/mdrip
|
||||
|
||||
# Version pinned by api/go.mod
|
||||
$(MYGOBIN)/stringer:
|
||||
go get golang.org/x/tools/cmd/stringer
|
||||
cd api; \
|
||||
go install golang.org/x/tools/cmd/stringer
|
||||
|
||||
# Version pinned by api/go.mod
|
||||
$(MYGOBIN)/goimports:
|
||||
go get golang.org/x/tools/cmd/goimports
|
||||
cd api; \
|
||||
go install golang.org/x/tools/cmd/goimports
|
||||
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/gorepomod:
|
||||
cd cmd/gorepomod; \
|
||||
go install .
|
||||
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/k8scopy:
|
||||
cd cmd/k8scopy; \
|
||||
go install .
|
||||
|
||||
# Build from local source.
|
||||
# To pin pluginator, use this recipe instead:
|
||||
# cd api;
|
||||
# go install sigs.k8s.io/kustomize/pluginator/v2
|
||||
$(MYGOBIN)/pluginator:
|
||||
cd cmd/pluginator; \
|
||||
cd pluginator; \
|
||||
go install .
|
||||
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/prchecker:
|
||||
cd cmd/prchecker; \
|
||||
go install .
|
||||
|
||||
# Build from local source.
|
||||
$(MYGOBIN)/kustomize: build-kustomize-api
|
||||
# Install kustomize from whatever is checked out.
|
||||
$(MYGOBIN)/kustomize:
|
||||
cd kustomize; \
|
||||
go install .
|
||||
|
||||
@@ -97,12 +64,8 @@ $(MYGOBIN)/kustomize: build-kustomize-api
|
||||
install-tools: \
|
||||
$(MYGOBIN)/goimports \
|
||||
$(MYGOBIN)/golangci-lint-kustomize \
|
||||
$(MYGOBIN)/gorepomod \
|
||||
$(MYGOBIN)/helmV3 \
|
||||
$(MYGOBIN)/k8scopy \
|
||||
$(MYGOBIN)/mdrip \
|
||||
$(MYGOBIN)/pluginator \
|
||||
$(MYGOBIN)/prchecker \
|
||||
$(MYGOBIN)/stringer
|
||||
|
||||
### Begin kustomize plugin rules.
|
||||
@@ -134,9 +97,9 @@ pSrc=plugin/builtin
|
||||
_builtinplugins = \
|
||||
AnnotationsTransformer.go \
|
||||
ConfigMapGenerator.go \
|
||||
IAMPolicyGenerator.go \
|
||||
HashTransformer.go \
|
||||
ImageTagTransformer.go \
|
||||
InventoryTransformer.go \
|
||||
LabelTransformer.go \
|
||||
LegacyOrderTransformer.go \
|
||||
NamespaceTransformer.go \
|
||||
@@ -144,11 +107,8 @@ _builtinplugins = \
|
||||
PatchStrategicMergeTransformer.go \
|
||||
PatchTransformer.go \
|
||||
PrefixSuffixTransformer.go \
|
||||
ReplacementTransformer.go \
|
||||
ReplicaCountTransformer.go \
|
||||
SecretGenerator.go \
|
||||
ValueAddTransformer.go \
|
||||
HelmChartInflationGenerator.go
|
||||
SecretGenerator.go
|
||||
|
||||
# Maintaining this explicit list of generated files, and
|
||||
# adding it as a dependency to a few targets, to assure
|
||||
@@ -162,9 +122,9 @@ builtinplugins = $(patsubst %,$(pGen)/%,$(_builtinplugins))
|
||||
# that file, will be recreated.
|
||||
$(pGen)/AnnotationsTransformer.go: $(pSrc)/annotationstransformer/AnnotationsTransformer.go
|
||||
$(pGen)/ConfigMapGenerator.go: $(pSrc)/configmapgenerator/ConfigMapGenerator.go
|
||||
$(pGen)/GkeSaGenerator.go: $(pSrc)/gkesagenerator/GkeSaGenerator.go
|
||||
$(pGen)/HashTransformer.go: $(pSrc)/hashtransformer/HashTransformer.go
|
||||
$(pGen)/ImageTagTransformer.go: $(pSrc)/imagetagtransformer/ImageTagTransformer.go
|
||||
$(pGen)/InventoryTransformer.go: $(pSrc)/inventorytransformer/InventoryTransformer.go
|
||||
$(pGen)/LabelTransformer.go: $(pSrc)/labeltransformer/LabelTransformer.go
|
||||
$(pGen)/LegacyOrderTransformer.go: $(pSrc)/legacyordertransformer/LegacyOrderTransformer.go
|
||||
$(pGen)/NamespaceTransformer.go: $(pSrc)/namespacetransformer/NamespaceTransformer.go
|
||||
@@ -172,11 +132,8 @@ $(pGen)/PatchJson6902Transformer.go: $(pSrc)/patchjson6902transformer/PatchJson6
|
||||
$(pGen)/PatchStrategicMergeTransformer.go: $(pSrc)/patchstrategicmergetransformer/PatchStrategicMergeTransformer.go
|
||||
$(pGen)/PatchTransformer.go: $(pSrc)/patchtransformer/PatchTransformer.go
|
||||
$(pGen)/PrefixSuffixTransformer.go: $(pSrc)/prefixsuffixtransformer/PrefixSuffixTransformer.go
|
||||
$(pGen)/ReplacementTransformer.go: $(pSrc)/replacementtransformer/ReplacementTransformer.go
|
||||
$(pGen)/ReplicaCountTransformer.go: $(pSrc)/replicacounttransformer/ReplicaCountTransformer.go
|
||||
$(pGen)/SecretGenerator.go: $(pSrc)/secretgenerator/SecretGenerator.go
|
||||
$(pGen)/ValueAddTransformer.go: $(pSrc)/valueaddtransformer/ValueAddTransformer.go
|
||||
$(pGen)/HelmChartInflationGenerator.go: $(pSrc)/helmchartinflationgenerator/HelmChartInflationGenerator.go
|
||||
|
||||
# The (verbose but portable) Makefile way to convert to lowercase.
|
||||
toLowerCase = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1))))))))))))))))))))))))))
|
||||
@@ -195,42 +152,28 @@ $(pGen)/%.go: $(MYGOBIN)/pluginator
|
||||
.PHONY: generate-kustomize-builtin-plugins
|
||||
generate-kustomize-builtin-plugins: $(builtinplugins)
|
||||
|
||||
.PHONY: build-kustomize-external-go-plugin
|
||||
build-kustomize-external-go-plugin:
|
||||
.PHONY: kustomize-external-go-plugin-build
|
||||
kustomize-external-go-plugin-build:
|
||||
./hack/buildExternalGoPlugins.sh ./plugin
|
||||
|
||||
.PHONY: clean-kustomize-external-go-plugin
|
||||
clean-kustomize-external-go-plugin:
|
||||
.PHONY: kustomize-external-go-plugin-clean
|
||||
kustomize-external-go-plugin-clean:
|
||||
./hack/buildExternalGoPlugins.sh ./plugin clean
|
||||
|
||||
### End kustomize plugin rules.
|
||||
|
||||
.PHONY: lint-kustomize
|
||||
lint-kustomize: $(MYGOBIN)/golangci-lint-kustomize $(builtinplugins)
|
||||
cd api; $(MYGOBIN)/golangci-lint-kustomize \
|
||||
-c ../.golangci-kustomize.yml \
|
||||
run ./...
|
||||
cd kustomize; $(MYGOBIN)/golangci-lint-kustomize \
|
||||
-c ../.golangci-kustomize.yml \
|
||||
run ./...
|
||||
cd cmd/pluginator; $(MYGOBIN)/golangci-lint-kustomize \
|
||||
-c ../../.golangci-kustomize.yml \
|
||||
run ./...
|
||||
|
||||
# Used to add non-default compilation flags when experimenting with
|
||||
# plugin-to-api compatibility checks.
|
||||
.PHONY: build-kustomize-api
|
||||
build-kustomize-api: $(builtinplugins)
|
||||
cd api; go build ./...
|
||||
|
||||
.PHONY: generate-kustomize-api
|
||||
generate-kustomize-api: $(MYGOBIN)/k8scopy
|
||||
cd api; go generate ./...
|
||||
lint-kustomize: install-tools $(builtinplugins)
|
||||
cd api; \
|
||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
||||
cd kustomize; \
|
||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
||||
cd pluginator; \
|
||||
$(MYGOBIN)/golangci-lint-kustomize -c ../.golangci-kustomize.yml run ./...
|
||||
|
||||
.PHONY: test-unit-kustomize-api
|
||||
test-unit-kustomize-api: build-kustomize-api
|
||||
cd api; go test ./... -ldflags "-X sigs.k8s.io/kustomize/api/provenance.version=v444.333.222"; \
|
||||
cd krusty; OPENAPI_TEST=true go test -run TestCustomOpenAPIFieldFromComponentWithOverlays
|
||||
test-unit-kustomize-api: $(builtinplugins)
|
||||
cd api; go test ./...
|
||||
|
||||
.PHONY: test-unit-kustomize-plugins
|
||||
test-unit-kustomize-plugins:
|
||||
@@ -246,42 +189,21 @@ test-unit-kustomize-all: \
|
||||
test-unit-kustomize-cli \
|
||||
test-unit-kustomize-plugins
|
||||
|
||||
test-unit-cmd-all:
|
||||
./hack/kyaml-pre-commit.sh
|
||||
|
||||
test-go-mod:
|
||||
./hack/check-go-mod.sh
|
||||
|
||||
# Environment variables are defined at
|
||||
# https://github.com/kubernetes/test-infra/blob/master/prow/jobs.md#job-environment-variables
|
||||
.PHONY: test-multi-module
|
||||
test-multi-module: $(MYGOBIN)/prchecker
|
||||
( \
|
||||
export MYGOBIN=$(MYGOBIN); \
|
||||
export REPO_OWNER=$(REPO_OWNER); \
|
||||
export REPO_NAME=$(REPO_NAME); \
|
||||
export PULL_NUMBER=$(PULL_NUMBER); \
|
||||
export MODULES=$(MODULES); \
|
||||
./hack/check-multi-module.sh; \
|
||||
)
|
||||
|
||||
.PHONY:
|
||||
test-examples-e2e-kustomize: $(MYGOBIN)/mdrip $(MYGOBIN)/kind
|
||||
( \
|
||||
set -e; \
|
||||
/bin/rm -f $(MYGOBIN)/kustomize; \
|
||||
echo "Installing kustomize from ."; \
|
||||
cd kustomize; go install .; cd ..; \
|
||||
./hack/testExamplesE2EAgainstKustomize.sh .; \
|
||||
)
|
||||
|
||||
.PHONY:
|
||||
test-examples-kustomize-against-HEAD: $(MYGOBIN)/kustomize $(MYGOBIN)/mdrip
|
||||
./hack/testExamplesAgainstKustomize.sh HEAD
|
||||
|
||||
.PHONY:
|
||||
test-examples-kustomize-against-v4-release: $(MYGOBIN)/mdrip
|
||||
./hack/testExamplesAgainstKustomize.sh v4@$(LATEST_V4_RELEASE)
|
||||
test-examples-kustomize-against-latest: $(MYGOBIN)/mdrip
|
||||
( \
|
||||
set -e; \
|
||||
/bin/rm -f $(MYGOBIN)/kustomize; \
|
||||
echo "Installing kustomize from latest."; \
|
||||
GO111MODULE=on go install sigs.k8s.io/kustomize/kustomize/v3; \
|
||||
./hack/testExamplesAgainstKustomize.sh latest; \
|
||||
echo "Reinstalling kustomize from HEAD."; \
|
||||
cd kustomize; go install .; \
|
||||
)
|
||||
|
||||
# linux only.
|
||||
# This is for testing an example plugin that
|
||||
@@ -293,78 +215,36 @@ $(MYGOBIN)/kubeval:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-$(GOOS)-$(GOARCH).tar.gz; \
|
||||
tar xf kubeval-$(GOOS)-$(GOARCH).tar.gz; \
|
||||
wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz; \
|
||||
tar xf kubeval-linux-amd64.tar.gz; \
|
||||
mv kubeval $(MYGOBIN); \
|
||||
rm -rf $$d; \
|
||||
)
|
||||
|
||||
# linux only.
|
||||
# This is for testing an example plugin that uses helm to inflate a chart
|
||||
# for subsequent kustomization.
|
||||
# Don't want to add a hard dependence in go.mod file to helm.
|
||||
# Instead, download the binaries.
|
||||
$(MYGOBIN)/helmV2:
|
||||
# This is for testing an example plugin that
|
||||
# uses helm to inflate a chart for subsequent kustomization.
|
||||
# Don't want to add a hard dependence in go.mod file
|
||||
# to helm.
|
||||
# Instead, download the binary.
|
||||
$(MYGOBIN)/helm:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
tgzFile=helm-v2.13.1-$(GOOS)-$(GOARCH).tar.gz; \
|
||||
wget https://storage.googleapis.com/kubernetes-helm/$$tgzFile; \
|
||||
tar -xvzf $$tgzFile; \
|
||||
mv $(GOOS)-$(GOARCH)/helm $(MYGOBIN)/helmV2; \
|
||||
rm -rf $$d \
|
||||
)
|
||||
|
||||
# Helm V3 differs from helm V2; downloading it to provide coverage for the
|
||||
# chart inflator plugin under helm v3.
|
||||
$(MYGOBIN)/helmV3:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
tgzFile=helm-v3.6.3-$(GOOS)-$(GOARCH).tar.gz; \
|
||||
wget https://get.helm.sh/$$tgzFile; \
|
||||
tar -xvzf $$tgzFile; \
|
||||
mv $(GOOS)-$(GOARCH)/helm $(MYGOBIN)/helmV3; \
|
||||
rm -rf $$d \
|
||||
)
|
||||
|
||||
$(MYGOBIN)/kind:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
wget -O ./kind https://github.com/kubernetes-sigs/kind/releases/download/v0.7.0/kind-$(GOOS)-$(GOARCH); \
|
||||
chmod +x ./kind; \
|
||||
mv ./kind $(MYGOBIN); \
|
||||
rm -rf $$d; \
|
||||
)
|
||||
|
||||
# linux only.
|
||||
$(MYGOBIN)/gh:
|
||||
( \
|
||||
set -e; \
|
||||
d=$(shell mktemp -d); cd $$d; \
|
||||
tgzFile=gh_1.0.0_$(GOOS)_$(GOARCH).tar.gz; \
|
||||
wget https://github.com/cli/cli/releases/download/v1.0.0/$$tgzFile; \
|
||||
tar -xvzf $$tgzFile; \
|
||||
mv gh_1.0.0_$(GOOS)_$(GOARCH)/bin/gh $(MYGOBIN)/gh; \
|
||||
wget https://storage.googleapis.com/kubernetes-helm/helm-v2.13.1-linux-amd64.tar.gz; \
|
||||
tar -xvzf helm-v2.13.1-linux-amd64.tar.gz; \
|
||||
mv linux-amd64/helm $(MYGOBIN); \
|
||||
rm -rf $$d \
|
||||
)
|
||||
|
||||
.PHONY: clean
|
||||
clean: clean-kustomize-external-go-plugin
|
||||
clean: kustomize-external-go-plugin-clean
|
||||
go clean --cache
|
||||
rm -f $(builtinplugins)
|
||||
rm -f $(MYGOBIN)/goimports
|
||||
rm -f $(MYGOBIN)/golangci-lint-kustomize
|
||||
rm -f $(MYGOBIN)/pluginator
|
||||
rm -f $(MYGOBIN)/kustomize
|
||||
rm -f $(MYGOBIN)/mdrip
|
||||
rm -f $(MYGOBIN)/prchecker
|
||||
rm -f $(MYGOBIN)/stringer
|
||||
rm -f $(MYGOBIN)/golangci-lint-kustomize
|
||||
|
||||
# Handle pluginator manually.
|
||||
# rm -f $(MYGOBIN)/pluginator
|
||||
|
||||
# Nuke the site from orbit. It's the only way to be sure.
|
||||
.PHONY: nuke
|
||||
nuke: clean
|
||||
go clean --modcache
|
||||
sudo rm -rf $(shell go env GOPATH)/pkg/mod/sigs.k8s.io
|
||||
|
||||
6
OWNERS
6
OWNERS
@@ -1,6 +1,4 @@
|
||||
# See https://github.com/kubernetes/community/blob/master/community-membership.md
|
||||
approvers:
|
||||
- kustomize-approvers
|
||||
|
||||
reviewers:
|
||||
- kustomize-reviewers
|
||||
- kustomize-admins
|
||||
- kustomize-maintainers
|
||||
|
||||
@@ -1,30 +1,11 @@
|
||||
# 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-owners:
|
||||
- knverey
|
||||
kustomize-admins:
|
||||
- monopole
|
||||
- pwittrock
|
||||
kustomize-approvers:
|
||||
kustomize-maintainers:
|
||||
- droot
|
||||
- justinsb
|
||||
- knverey
|
||||
- monopole
|
||||
- natasha41575
|
||||
- pwittrock
|
||||
kustomize-reviewers:
|
||||
- knverey
|
||||
- monopole
|
||||
- natasha41575
|
||||
|
||||
kyaml-approvers:
|
||||
- mengqiy
|
||||
- mortent
|
||||
- phanimarupaka
|
||||
kyaml-reviewers:
|
||||
- mengqiy
|
||||
- mortent
|
||||
- phanimarupaka
|
||||
|
||||
emeritus-approvers:
|
||||
- liujingfang1
|
||||
- Shell32-Natsu
|
||||
- mengqiy
|
||||
- monopole
|
||||
- pwittrock
|
||||
|
||||
99
README.md
99
README.md
@@ -9,38 +9,30 @@ patch [kubernetes style] API objects. It's like
|
||||
[`make`], in that what it does is declared in a file,
|
||||
and it's like [`sed`], in that it emits edited text.
|
||||
|
||||
This tool is sponsored by [sig-cli] ([KEP]).
|
||||
This tool is sponsored by [sig-cli] ([KEP]), and
|
||||
inspired by [DAM].
|
||||
|
||||
- [Installation instructions](https://kubernetes-sigs.github.io/kustomize/installation)
|
||||
- [General documentation](https://kubernetes-sigs.github.io/kustomize)
|
||||
- [Examples](examples)
|
||||
|
||||
[](https://prow.k8s.io/job-history/kubernetes-jenkins/pr-logs/directory/kustomize-presubmit-master)
|
||||
[](https://travis-ci.org/kubernetes-sigs/kustomize)
|
||||
[](https://goreportcard.com/report/github.com/kubernetes-sigs/kustomize)
|
||||
|
||||
Download a binary from the [release page], or see
|
||||
these [instructions](docs/INSTALL.md).
|
||||
|
||||
Browse the [docs](docs) or jump right into the
|
||||
tested [examples](examples).
|
||||
|
||||
## kubectl integration
|
||||
|
||||
The kustomize build flow at [v2.0.3] was added
|
||||
to [kubectl v1.14][kubectl announcement]. The kustomize
|
||||
flow in kubectl remained frozen at v2.0.3 until kubectl v1.21,
|
||||
which [updated it to v4.0.5][kust-in-kubectl update]. It will
|
||||
be updated on a regular basis going forward, and such updates
|
||||
will be reflected in the Kubernetes release notes.
|
||||
Since [v1.14][kubectl announcement] the kustomize build system has been included in kubectl.
|
||||
|
||||
| Kubectl version | Kustomize version |
|
||||
| --- | --- |
|
||||
| < v1.14 | n/a |
|
||||
| v1.14-v1.20 | v2.0.3 |
|
||||
| v1.21 | v4.0.5 |
|
||||
| v1.22 | v4.2.0 |
|
||||
| kubectl version | kustomize version |
|
||||
|---------|--------|
|
||||
| v1.16.0 | [v2.0.3](/../../tree/v2.0.3) |
|
||||
| v1.15.x | [v2.0.3](/../../tree/v2.0.3) |
|
||||
| v1.14.x | [v2.0.3](/../../tree/v2.0.3) |
|
||||
|
||||
[v2.0.3]: /../../tree/v2.0.3
|
||||
[#2506]: https://github.com/kubernetes-sigs/kustomize/issues/2506
|
||||
[#1500]: https://github.com/kubernetes-sigs/kustomize/issues/1500
|
||||
[kust-in-kubectl update]: https://github.com/kubernetes/kubernetes/blob/4d75a6238a6e330337526e0513e67d02b1940b63/CHANGELOG/CHANGELOG-1.21.md#kustomize-updates-in-kubectl
|
||||
|
||||
For examples and guides for using the kubectl integration please
|
||||
see the [kubectl book] or the [kubernetes documentation].
|
||||
For examples and guides for using the kubectl integration please see the [kubectl book] or the [kubernetes documentation].
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -115,7 +107,7 @@ Take the work from step (1) above, move it into a
|
||||
`someApp` subdirectory called `base`, then
|
||||
place overlays in a sibling directory.
|
||||
|
||||
An overlay is just another kustomization, referring to
|
||||
An overlay is just another kustomization, refering to
|
||||
the base, and referring to patches to apply to that
|
||||
base.
|
||||
|
||||
@@ -141,8 +133,20 @@ The YAML can be directly [applied] to a cluster:
|
||||
|
||||
## Community
|
||||
|
||||
- [file a bug](https://kubernetes-sigs.github.io/kustomize/contributing/bugs/) instructions
|
||||
- [contribute a feature](https://kubernetes-sigs.github.io/kustomize/contributing/features/) instructions
|
||||
To file bugs please read [this](docs/bugs.md).
|
||||
|
||||
Before working on an implementation, please
|
||||
|
||||
* Read the [eschewed feature list].
|
||||
* File an issue describing
|
||||
how the new feature would behave
|
||||
and label it [kind/feature].
|
||||
|
||||
### Other communication channels
|
||||
|
||||
- [Slack]
|
||||
- [Mailing List]
|
||||
- General kubernetes [community page]
|
||||
|
||||
### Code of conduct
|
||||
|
||||
@@ -151,27 +155,32 @@ is governed by the [Kubernetes Code of Conduct].
|
||||
|
||||
[`make`]: https://www.gnu.org/software/make
|
||||
[`sed`]: https://www.gnu.org/software/sed
|
||||
[DAM]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#declarative-application-management
|
||||
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/2377-Kustomize/README.md
|
||||
[DAM]: docs/glossary.md#declarative-application-management
|
||||
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/0008-kustomize.md
|
||||
[Kubernetes Code of Conduct]: code-of-conduct.md
|
||||
[applied]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#apply
|
||||
[base]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#base
|
||||
[declarative configuration]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#declarative-application-management
|
||||
[Mailing List]: https://groups.google.com/forum/#!forum/kubernetes-sig-cli
|
||||
[Slack]: https://kubernetes.slack.com/messages/sig-cli
|
||||
[applied]: docs/glossary.md#apply
|
||||
[base]: docs/glossary.md#base
|
||||
[community page]: http://kubernetes.io/community/
|
||||
[declarative configuration]: docs/glossary.md#declarative-application-management
|
||||
[eschewed feature list]: docs/eschewedFeatures.md
|
||||
[imageBase]: docs/images/base.jpg
|
||||
[imageOverlay]: docs/images/overlay.jpg
|
||||
[kind/feature]: /../../labels/kind%2Ffeature
|
||||
[kubectl announcement]: https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement
|
||||
[kubectl book]: https://kubectl.docs.kubernetes.io/guides/introduction/kustomize/
|
||||
[kubectl book]: https://kubectl.docs.kubernetes.io/pages/app_customization/introduction.html
|
||||
[kubernetes documentation]: https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/
|
||||
[kubernetes style]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#kubernetes-style-object
|
||||
[kustomization]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#kustomization
|
||||
[overlay]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#overlay
|
||||
[overlays]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#overlay
|
||||
[release page]: https://github.com/kubernetes-sigs/kustomize/releases
|
||||
[resource]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#resource
|
||||
[resources]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#resource
|
||||
[kubernetes style]: docs/glossary.md#kubernetes-style-object
|
||||
[kustomization]: docs/glossary.md#kustomization
|
||||
[overlay]: docs/glossary.md#overlay
|
||||
[overlays]: docs/glossary.md#overlay
|
||||
[release page]: /../../releases
|
||||
[resource]: docs/glossary.md#resource
|
||||
[resources]: docs/glossary.md#resource
|
||||
[sig-cli]: https://github.com/kubernetes/community/blob/master/sig-cli/README.md
|
||||
[variant]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#variant
|
||||
[variants]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#variant
|
||||
[v2.0.3]: https://github.com/kubernetes-sigs/kustomize/releases/tag/v2.0.3
|
||||
[v2.1.0]: https://github.com/kubernetes-sigs/kustomize/releases/tag/v2.1.0
|
||||
[workflows]: https://kubernetes-sigs.github.io/kustomize/guides
|
||||
[variant]: docs/glossary.md#variant
|
||||
[variants]: docs/glossary.md#variant
|
||||
[v2.0.3]: /../../releases/tag/v2.0.3
|
||||
[v2.1.0]: /../../releases/tag/v2.1.0
|
||||
[workflows]: docs/workflows.md
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/annotations"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
@@ -24,13 +24,14 @@ func (p *AnnotationsTransformerPlugin) Config(
|
||||
}
|
||||
|
||||
func (p *AnnotationsTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
if len(p.Annotations) == 0 {
|
||||
return nil
|
||||
t, err := transform.NewMapTransformer(
|
||||
p.FieldSpecs,
|
||||
p.Annotations,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.ApplyFilter(annotations.Filter{
|
||||
Annotations: p.Annotations,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
return t.Transform(m)
|
||||
}
|
||||
|
||||
func NewAnnotationsTransformerPlugin() resmap.TransformerPlugin {
|
||||
|
||||
@@ -13,10 +13,13 @@ import (
|
||||
type ConfigMapGeneratorPlugin struct {
|
||||
h *resmap.PluginHelpers
|
||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
types.GeneratorOptions
|
||||
types.ConfigMapArgs
|
||||
}
|
||||
|
||||
func (p *ConfigMapGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
|
||||
func (p *ConfigMapGeneratorPlugin) Config(
|
||||
h *resmap.PluginHelpers, config []byte) (err error) {
|
||||
p.GeneratorOptions = types.GeneratorOptions{}
|
||||
p.ConfigMapArgs = types.ConfigMapArgs{}
|
||||
err = yaml.Unmarshal(config, p)
|
||||
if p.ConfigMapArgs.Name == "" {
|
||||
@@ -31,7 +34,8 @@ func (p *ConfigMapGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte
|
||||
|
||||
func (p *ConfigMapGeneratorPlugin) Generate() (resmap.ResMap, error) {
|
||||
return p.h.ResmapFactory().FromConfigMapArgs(
|
||||
kv.NewLoader(p.h.Loader(), p.h.Validator()), p.ConfigMapArgs)
|
||||
kv.NewLoader(p.h.Loader(), p.h.Validator()),
|
||||
&p.GeneratorOptions, p.ConfigMapArgs)
|
||||
}
|
||||
|
||||
func NewConfigMapGeneratorPlugin() resmap.GeneratorPlugin {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
type HashTransformerPlugin struct {
|
||||
hasher ifc.KustHasher
|
||||
hasher ifc.KunstructuredHasher
|
||||
}
|
||||
|
||||
func (p *HashTransformerPlugin) Config(
|
||||
@@ -24,11 +24,10 @@ func (p *HashTransformerPlugin) Config(
|
||||
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
for _, res := range m.Resources() {
|
||||
if res.NeedHashSuffix() {
|
||||
h, err := res.Hash(p.hasher)
|
||||
h, err := p.hasher.Hash(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res.StorePreviousId()
|
||||
res.SetName(fmt.Sprintf("%s-%s", res.GetName(), h))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,339 +0,0 @@
|
||||
// Code generated by pluginator on HelmChartInflationGenerator; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// HelmChartInflationGeneratorPlugin is a plugin to generate resources
|
||||
// from a remote or local helm chart.
|
||||
type HelmChartInflationGeneratorPlugin struct {
|
||||
h *resmap.PluginHelpers
|
||||
types.HelmGlobals
|
||||
types.HelmChart
|
||||
tmpDir string
|
||||
}
|
||||
|
||||
var KustomizePlugin HelmChartInflationGeneratorPlugin
|
||||
|
||||
const (
|
||||
valuesMergeOptionMerge = "merge"
|
||||
valuesMergeOptionOverride = "override"
|
||||
valuesMergeOptionReplace = "replace"
|
||||
)
|
||||
|
||||
var legalMergeOptions = []string{
|
||||
valuesMergeOptionMerge,
|
||||
valuesMergeOptionOverride,
|
||||
valuesMergeOptionReplace,
|
||||
}
|
||||
|
||||
// Config uses the input plugin configurations `config` to setup the generator
|
||||
// options
|
||||
func (p *HelmChartInflationGeneratorPlugin) Config(
|
||||
h *resmap.PluginHelpers, config []byte) (err error) {
|
||||
if h.GeneralConfig() == nil {
|
||||
return fmt.Errorf("unable to access general config")
|
||||
}
|
||||
if !h.GeneralConfig().HelmConfig.Enabled {
|
||||
return fmt.Errorf("must specify --enable-helm")
|
||||
}
|
||||
if h.GeneralConfig().HelmConfig.Command == "" {
|
||||
return fmt.Errorf("must specify --helm-command")
|
||||
}
|
||||
p.h = h
|
||||
if err = yaml.Unmarshal(config, p); err != nil {
|
||||
return
|
||||
}
|
||||
return p.validateArgs()
|
||||
}
|
||||
|
||||
// This uses the real file system since tmpDir may be used
|
||||
// by the helm subprocess. Cannot use a chroot jail or fake
|
||||
// filesystem since we allow the user to use previously
|
||||
// downloaded charts. This is safe since this plugin is
|
||||
// owned by kustomize.
|
||||
func (p *HelmChartInflationGeneratorPlugin) establishTmpDir() (err error) {
|
||||
if p.tmpDir != "" {
|
||||
// already done.
|
||||
return nil
|
||||
}
|
||||
p.tmpDir, err = ioutil.TempDir("", "kustomize-helm-")
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) validateArgs() (err error) {
|
||||
if p.Name == "" {
|
||||
return fmt.Errorf("chart name cannot be empty")
|
||||
}
|
||||
|
||||
// ChartHome might be consulted by the plugin (to read
|
||||
// values files below it), so it must be located under
|
||||
// the loader root (unless root restrictions are
|
||||
// disabled, in which case this can be an absolute path).
|
||||
if p.ChartHome == "" {
|
||||
p.ChartHome = "charts"
|
||||
}
|
||||
|
||||
// The ValuesFile may be consulted by the plugin, so it must
|
||||
// be under the loader root (unless root restrictions are
|
||||
// disabled).
|
||||
if p.ValuesFile == "" {
|
||||
p.ValuesFile = filepath.Join(p.ChartHome, p.Name, "values.yaml")
|
||||
}
|
||||
|
||||
if err = p.errIfIllegalValuesMerge(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ConfigHome is not loaded by the plugin, and can be located anywhere.
|
||||
if p.ConfigHome == "" {
|
||||
if err = p.establishTmpDir(); err != nil {
|
||||
return errors.Wrap(
|
||||
err, "unable to create tmp dir for HELM_CONFIG_HOME")
|
||||
}
|
||||
p.ConfigHome = filepath.Join(p.tmpDir, "helm")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) errIfIllegalValuesMerge() error {
|
||||
if p.ValuesMerge == "" {
|
||||
// Use the default.
|
||||
p.ValuesMerge = valuesMergeOptionOverride
|
||||
return nil
|
||||
}
|
||||
for _, opt := range legalMergeOptions {
|
||||
if p.ValuesMerge == opt {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("valuesMerge must be one of %v", legalMergeOptions)
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) absChartHome() string {
|
||||
if filepath.IsAbs(p.ChartHome) {
|
||||
return p.ChartHome
|
||||
}
|
||||
return filepath.Join(p.h.Loader().Root(), p.ChartHome)
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) runHelmCommand(
|
||||
args []string) ([]byte, error) {
|
||||
stdout := new(bytes.Buffer)
|
||||
stderr := new(bytes.Buffer)
|
||||
cmd := exec.Command(p.h.GeneralConfig().HelmConfig.Command, args...)
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
env := []string{
|
||||
fmt.Sprintf("HELM_CONFIG_HOME=%s", p.ConfigHome),
|
||||
fmt.Sprintf("HELM_CACHE_HOME=%s/.cache", p.ConfigHome),
|
||||
fmt.Sprintf("HELM_DATA_HOME=%s/.data", p.ConfigHome)}
|
||||
cmd.Env = append(os.Environ(), env...)
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
helm := p.h.GeneralConfig().HelmConfig.Command
|
||||
err = errors.Wrap(
|
||||
fmt.Errorf(
|
||||
"unable to run: '%s %s' with env=%s (is '%s' installed?)",
|
||||
helm, strings.Join(args, " "), env, helm),
|
||||
stderr.String(),
|
||||
)
|
||||
}
|
||||
return stdout.Bytes(), err
|
||||
}
|
||||
|
||||
// createNewMergedValuesFile replaces/merges original values file with ValuesInline.
|
||||
func (p *HelmChartInflationGeneratorPlugin) createNewMergedValuesFile() (
|
||||
path string, err error) {
|
||||
if p.ValuesMerge == valuesMergeOptionMerge ||
|
||||
p.ValuesMerge == valuesMergeOptionOverride {
|
||||
if err = p.replaceValuesInline(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
var b []byte
|
||||
b, err = yaml.Marshal(p.ValuesInline)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return p.writeValuesBytes(b)
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) replaceValuesInline() error {
|
||||
pValues, err := p.h.Loader().Load(p.ValuesFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chValues := make(map[string]interface{})
|
||||
if err = yaml.Unmarshal(pValues, &chValues); err != nil {
|
||||
return err
|
||||
}
|
||||
switch p.ValuesMerge {
|
||||
case valuesMergeOptionOverride:
|
||||
err = mergo.Merge(
|
||||
&chValues, p.ValuesInline, mergo.WithOverride)
|
||||
case valuesMergeOptionMerge:
|
||||
err = mergo.Merge(&chValues, p.ValuesInline)
|
||||
}
|
||||
p.ValuesInline = chValues
|
||||
return err
|
||||
}
|
||||
|
||||
// copyValuesFile to avoid branching. TODO: get rid of this.
|
||||
func (p *HelmChartInflationGeneratorPlugin) copyValuesFile() (string, error) {
|
||||
b, err := p.h.Loader().Load(p.ValuesFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return p.writeValuesBytes(b)
|
||||
}
|
||||
|
||||
// Write a absolute path file in the tmp file system.
|
||||
func (p *HelmChartInflationGeneratorPlugin) writeValuesBytes(
|
||||
b []byte) (string, error) {
|
||||
if err := p.establishTmpDir(); err != nil {
|
||||
return "", fmt.Errorf("cannot create tmp dir to write helm values")
|
||||
}
|
||||
path := filepath.Join(p.tmpDir, p.Name+"-kustomize-values.yaml")
|
||||
return path, ioutil.WriteFile(path, b, 0644)
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) cleanup() {
|
||||
if p.tmpDir != "" {
|
||||
os.RemoveAll(p.tmpDir)
|
||||
}
|
||||
}
|
||||
|
||||
// Generate implements generator
|
||||
func (p *HelmChartInflationGeneratorPlugin) Generate() (rm resmap.ResMap, err error) {
|
||||
defer p.cleanup()
|
||||
if err = p.checkHelmVersion(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if path, exists := p.chartExistsLocally(); !exists {
|
||||
if p.Repo == "" {
|
||||
return nil, fmt.Errorf(
|
||||
"no repo specified for pull, no chart found at '%s'", path)
|
||||
}
|
||||
if _, err := p.runHelmCommand(p.pullCommand()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if len(p.ValuesInline) > 0 {
|
||||
p.ValuesFile, err = p.createNewMergedValuesFile()
|
||||
} else {
|
||||
p.ValuesFile, err = p.copyValuesFile()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var stdout []byte
|
||||
stdout, err = p.runHelmCommand(p.templateCommand())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rm, err = p.h.ResmapFactory().NewResMapFromBytes(stdout)
|
||||
if err == nil {
|
||||
return rm, nil
|
||||
}
|
||||
// try to remove the contents before first "---" because
|
||||
// helm may produce messages to stdout before it
|
||||
stdoutStr := string(stdout)
|
||||
if idx := strings.Index(stdoutStr, "---"); idx != -1 {
|
||||
return p.h.ResmapFactory().NewResMapFromBytes([]byte(stdoutStr[idx:]))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) templateCommand() []string {
|
||||
args := []string{"template"}
|
||||
if p.ReleaseName != "" {
|
||||
args = append(args, p.ReleaseName)
|
||||
}
|
||||
if p.Namespace != "" {
|
||||
args = append(args, "--namespace", p.Namespace)
|
||||
}
|
||||
args = append(args, filepath.Join(p.absChartHome(), p.Name))
|
||||
if p.ValuesFile != "" {
|
||||
args = append(args, "--values", p.ValuesFile)
|
||||
}
|
||||
if p.ReleaseName == "" {
|
||||
// AFAICT, this doesn't work as intended due to a bug in helm.
|
||||
// See https://github.com/helm/helm/issues/6019
|
||||
// I've tried placing the flag before and after the name argument.
|
||||
args = append(args, "--generate-name")
|
||||
}
|
||||
if p.IncludeCRDs {
|
||||
args = append(args, "--include-crds")
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
func (p *HelmChartInflationGeneratorPlugin) pullCommand() []string {
|
||||
args := []string{
|
||||
"pull",
|
||||
"--untar",
|
||||
"--untardir", p.absChartHome(),
|
||||
"--repo", p.Repo,
|
||||
p.Name}
|
||||
if p.Version != "" {
|
||||
args = append(args, "--version", p.Version)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// chartExistsLocally will return true if the chart does exist in
|
||||
// local chart home.
|
||||
func (p *HelmChartInflationGeneratorPlugin) chartExistsLocally() (string, bool) {
|
||||
path := filepath.Join(p.absChartHome(), p.Name)
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
return path, s.IsDir()
|
||||
}
|
||||
|
||||
// checkHelmVersion will return an error if the helm version is not V3
|
||||
func (p *HelmChartInflationGeneratorPlugin) checkHelmVersion() error {
|
||||
stdout, err := p.runHelmCommand([]string{"version", "-c", "--short"})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := regexp.Compile(`v?\d+(\.\d+)+`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v := r.FindString(string(stdout))
|
||||
if v == "" {
|
||||
return fmt.Errorf("cannot find version string in %s", string(stdout))
|
||||
}
|
||||
if v[0] == 'v' {
|
||||
v = v[1:]
|
||||
}
|
||||
majorVersion := strings.Split(v, ".")[0]
|
||||
if majorVersion != "3" {
|
||||
return fmt.Errorf("this plugin requires helm V3 but got v%s", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHelmChartInflationGeneratorPlugin() resmap.GeneratorPlugin {
|
||||
return &HelmChartInflationGeneratorPlugin{}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
// Code generated by pluginator on IAMPolicyGenerator; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/iampolicygenerator"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type IAMPolicyGeneratorPlugin struct {
|
||||
types.IAMPolicyGeneratorArgs
|
||||
}
|
||||
|
||||
func (p *IAMPolicyGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
|
||||
p.IAMPolicyGeneratorArgs = types.IAMPolicyGeneratorArgs{}
|
||||
err = yaml.Unmarshal(config, p)
|
||||
return
|
||||
}
|
||||
|
||||
func (p *IAMPolicyGeneratorPlugin) Generate() (resmap.ResMap, error) {
|
||||
r := resmap.New()
|
||||
err := r.ApplyFilter(iampolicygenerator.Filter{
|
||||
IAMPolicyGenerator: p.IAMPolicyGeneratorArgs,
|
||||
})
|
||||
return r, err
|
||||
}
|
||||
|
||||
func NewIAMPolicyGeneratorPlugin() resmap.GeneratorPlugin {
|
||||
return &IAMPolicyGeneratorPlugin{}
|
||||
}
|
||||
@@ -4,9 +4,14 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/imagetag"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -25,15 +30,154 @@ func (p *ImageTagTransformerPlugin) Config(
|
||||
}
|
||||
|
||||
func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
if err := m.ApplyFilter(imagetag.LegacyFilter{
|
||||
ImageTag: p.ImageTag,
|
||||
}); err != nil {
|
||||
return err
|
||||
for _, r := range m.Resources() {
|
||||
for _, path := range p.FieldSpecs {
|
||||
if !r.OrgId().IsSelected(&path.Gvk) {
|
||||
continue
|
||||
}
|
||||
err := transform.MutateField(
|
||||
r.Map(), path.PathSlice(), false, p.mutateImage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Kept for backward compatibility
|
||||
if err := p.findAndReplaceImage(r.Map()); err != nil && r.OrgId().Kind != `CustomResourceDefinition` {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return m.ApplyFilter(imagetag.Filter{
|
||||
ImageTag: p.ImageTag,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ImageTagTransformerPlugin) mutateImage(in interface{}) (interface{}, error) {
|
||||
original, ok := in.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("image path is not of type string but %T", in)
|
||||
}
|
||||
|
||||
if !isImageMatched(original, p.ImageTag.Name) {
|
||||
return original, nil
|
||||
}
|
||||
name, tag := split(original)
|
||||
if p.ImageTag.NewName != "" {
|
||||
name = p.ImageTag.NewName
|
||||
}
|
||||
if p.ImageTag.NewTag != "" {
|
||||
tag = ":" + p.ImageTag.NewTag
|
||||
}
|
||||
if p.ImageTag.Digest != "" {
|
||||
tag = "@" + p.ImageTag.Digest
|
||||
}
|
||||
return name + tag, nil
|
||||
}
|
||||
|
||||
// findAndReplaceImage replaces the image name and
|
||||
// tags inside one object.
|
||||
// It searches the object for container session
|
||||
// then loops though all images inside containers
|
||||
// session, finds matched ones and update the
|
||||
// image name and tag name
|
||||
func (p *ImageTagTransformerPlugin) findAndReplaceImage(obj map[string]interface{}) error {
|
||||
paths := []string{"containers", "initContainers"}
|
||||
updated := false
|
||||
for _, path := range paths {
|
||||
containers, found := obj[path]
|
||||
if found && containers != nil {
|
||||
if _, err := p.updateContainers(containers); err != nil {
|
||||
return err
|
||||
}
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
if !updated {
|
||||
return p.findContainers(obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ImageTagTransformerPlugin) updateContainers(in interface{}) (interface{}, error) {
|
||||
containers, ok := in.([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(
|
||||
"containers path is not of type []interface{} but %T", in)
|
||||
}
|
||||
for i := range containers {
|
||||
container := containers[i].(map[string]interface{})
|
||||
containerImage, found := container["image"]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
imageName := containerImage.(string)
|
||||
if isImageMatched(imageName, p.ImageTag.Name) {
|
||||
newImage, err := p.mutateImage(imageName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
container["image"] = newImage
|
||||
}
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (p *ImageTagTransformerPlugin) findContainers(obj map[string]interface{}) error {
|
||||
for key := range obj {
|
||||
switch typedV := obj[key].(type) {
|
||||
case map[string]interface{}:
|
||||
err := p.findAndReplaceImage(typedV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case []interface{}:
|
||||
for i := range typedV {
|
||||
item := typedV[i]
|
||||
typedItem, ok := item.(map[string]interface{})
|
||||
if ok {
|
||||
err := p.findAndReplaceImage(typedItem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isImageMatched(s, t string) bool {
|
||||
// Tag values are limited to [a-zA-Z0-9_.-].
|
||||
pattern, _ := regexp.Compile("^" + t + "(@sha256)?(:[a-zA-Z0-9_.-]*)?$")
|
||||
return pattern.MatchString(s)
|
||||
}
|
||||
|
||||
// split separates and returns the name and tag parts
|
||||
// from the image string using either colon `:` or at `@` separators.
|
||||
// Note that the returned tag keeps its separator.
|
||||
func split(imageName string) (name string, tag string) {
|
||||
// check if image name contains a domain
|
||||
// if domain is present, ignore domain and check for `:`
|
||||
ic := -1
|
||||
if slashIndex := strings.Index(imageName, "/"); slashIndex < 0 {
|
||||
ic = strings.LastIndex(imageName, ":")
|
||||
} else {
|
||||
lastIc := strings.LastIndex(imageName[slashIndex:], ":")
|
||||
// set ic only if `:` is present
|
||||
if lastIc > 0 {
|
||||
ic = slashIndex + lastIc
|
||||
}
|
||||
}
|
||||
ia := strings.LastIndex(imageName, "@")
|
||||
if ic < 0 && ia < 0 {
|
||||
return imageName, ""
|
||||
}
|
||||
|
||||
i := ic
|
||||
if ia > 0 {
|
||||
i = ia
|
||||
}
|
||||
|
||||
name = imageName[:i]
|
||||
tag = imageName[i:]
|
||||
return
|
||||
}
|
||||
|
||||
func NewImageTagTransformerPlugin() resmap.TransformerPlugin {
|
||||
|
||||
128
api/builtins/InventoryTransformer.go
Normal file
128
api/builtins/InventoryTransformer.go
Normal file
@@ -0,0 +1,128 @@
|
||||
// Code generated by pluginator on InventoryTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/hasher"
|
||||
"sigs.k8s.io/kustomize/api/inventory"
|
||||
"sigs.k8s.io/kustomize/api/kv"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type InventoryTransformerPlugin struct {
|
||||
h *resmap.PluginHelpers
|
||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
Policy string `json:"policy,omitempty" yaml:"policy,omitempty"`
|
||||
}
|
||||
|
||||
func (p *InventoryTransformerPlugin) Config(
|
||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.h = h
|
||||
err = yaml.Unmarshal(c, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p.Policy == "" {
|
||||
p.Policy = types.GarbageIgnore.String()
|
||||
}
|
||||
if p.Policy != types.GarbageCollect.String() &&
|
||||
p.Policy != types.GarbageIgnore.String() {
|
||||
return fmt.Errorf(
|
||||
"unrecognized garbagePolicy '%s'", p.Policy)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Transform generates an inventory object from the input ResMap.
|
||||
// This ConfigMap supports the pruning command in
|
||||
// the client side tool proposed here:
|
||||
// https://github.com/kubernetes/enhancements/pull/810
|
||||
//
|
||||
// The inventory data is written to the ConfigMap's
|
||||
// annotations, rather than to the key-value pairs in
|
||||
// the ConfigMap's data field, since
|
||||
// 1. Keys in a ConfigMap's data field are too
|
||||
// constrained for this purpose.
|
||||
// 2. Using annotations allow any object to be used,
|
||||
// not just a ConfigMap, should some other object
|
||||
// (e.g. some App object) become more desirable
|
||||
// for this purpose.
|
||||
func (p *InventoryTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
|
||||
inv, h, err := makeInventory(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := types.ConfigMapArgs{}
|
||||
args.Name = p.Name
|
||||
args.Namespace = p.Namespace
|
||||
opts := &types.GeneratorOptions{
|
||||
Annotations: make(map[string]string),
|
||||
}
|
||||
opts.Annotations[inventory.HashAnnotation] = h
|
||||
err = inv.UpdateAnnotations(opts.Annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cm, err := p.h.ResmapFactory().RF().MakeConfigMap(
|
||||
kv.NewLoader(p.h.Loader(), p.h.Validator()), opts, &args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Policy == types.GarbageCollect.String() {
|
||||
for _, byeBye := range m.AllIds() {
|
||||
m.Remove(byeBye)
|
||||
}
|
||||
}
|
||||
return m.Append(cm)
|
||||
}
|
||||
|
||||
func makeInventory(m resmap.ResMap) (
|
||||
inv *inventory.Inventory, hash string, err error) {
|
||||
inv = inventory.NewInventory()
|
||||
var keys []string
|
||||
for _, r := range m.Resources() {
|
||||
ns := r.GetNamespace()
|
||||
item := resid.NewResIdWithNamespace(r.GetGvk(), r.GetName(), ns)
|
||||
if _, ok := inv.Current[item]; ok {
|
||||
return nil, "", fmt.Errorf(
|
||||
"item '%v' already in inventory", item)
|
||||
}
|
||||
inv.Current[item], err = computeRefs(r, m)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
keys = append(keys, item.String())
|
||||
}
|
||||
h, err := hasher.SortArrayAndComputeHash(keys)
|
||||
return inv, h, err
|
||||
}
|
||||
|
||||
func computeRefs(
|
||||
r *resource.Resource, m resmap.ResMap) (refs []resid.ResId, err error) {
|
||||
for _, refid := range r.GetRefBy() {
|
||||
ref, err := m.GetByCurrentId(refid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
refs = append(
|
||||
refs,
|
||||
resid.NewResIdWithNamespace(
|
||||
ref.GetGvk(), ref.GetName(), ref.GetNamespace()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewInventoryTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &InventoryTransformerPlugin{}
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/labels"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
@@ -24,13 +24,14 @@ func (p *LabelTransformerPlugin) Config(
|
||||
}
|
||||
|
||||
func (p *LabelTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
if len(p.Labels) == 0 {
|
||||
return nil
|
||||
t, err := transform.NewMapTransformer(
|
||||
p.FieldSpecs,
|
||||
p.Labels,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.ApplyFilter(labels.Filter{
|
||||
Labels: p.Labels,
|
||||
FsSlice: p.FieldSpecs,
|
||||
})
|
||||
return t.Transform(m)
|
||||
}
|
||||
|
||||
func NewLabelTransformerPlugin() resmap.TransformerPlugin {
|
||||
|
||||
@@ -6,8 +6,11 @@ package builtins
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/namespace"
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
@@ -30,26 +33,99 @@ func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
return nil
|
||||
}
|
||||
for _, r := range m.Resources() {
|
||||
if r.IsNilOrEmpty() {
|
||||
if len(r.Map()) == 0 {
|
||||
// Don't mutate empty objects?
|
||||
continue
|
||||
}
|
||||
r.StorePreviousId()
|
||||
if err := r.ApplyFilter(namespace.Filter{
|
||||
Namespace: p.Namespace,
|
||||
FsSlice: p.FieldSpecs,
|
||||
}); err != nil {
|
||||
return err
|
||||
|
||||
id := r.OrgId()
|
||||
applicableFs := p.applicableFieldSpecs(id)
|
||||
|
||||
for _, fs := range applicableFs {
|
||||
err := transform.MutateField(
|
||||
r.Map(), fs.PathSlice(), fs.CreateIfNotPresent,
|
||||
p.changeNamespace(r))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
matches := m.GetMatchingResourcesByCurrentId(r.CurId().Equals)
|
||||
if len(matches) != 1 {
|
||||
return fmt.Errorf(
|
||||
"namespace transformation produces ID conflict: %+v", matches)
|
||||
return fmt.Errorf("namespace tranformation produces ID conflict: %#v", matches)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const metaNamespace = "metadata/namespace"
|
||||
|
||||
// Special casing metadata.namespace since
|
||||
// all objects have it, even "ClusterKind" objects
|
||||
// that don't exist in a namespace (the Namespace
|
||||
// object itself doesn't live in a namespace).
|
||||
func (p *NamespaceTransformerPlugin) applicableFieldSpecs(id resid.ResId) []types.FieldSpec {
|
||||
var res []types.FieldSpec
|
||||
for _, fs := range p.FieldSpecs {
|
||||
if id.IsSelected(&fs.Gvk) && (fs.Path != metaNamespace || (fs.Path == metaNamespace && id.IsNamespaceableKind())) {
|
||||
res = append(res, fs)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (p *NamespaceTransformerPlugin) changeNamespace(
|
||||
_ *resource.Resource) func(in interface{}) (interface{}, error) {
|
||||
return func(in interface{}) (interface{}, error) {
|
||||
switch in.(type) {
|
||||
case string:
|
||||
// will happen when the metadata/namespace
|
||||
// value is replaced
|
||||
return p.Namespace, nil
|
||||
case []interface{}:
|
||||
l, _ := in.([]interface{})
|
||||
for idx, item := range l {
|
||||
switch item.(type) {
|
||||
case map[string]interface{}:
|
||||
// Will happen when mutating the subjects
|
||||
// field of ClusterRoleBinding and RoleBinding
|
||||
inMap, _ := item.(map[string]interface{})
|
||||
if _, ok := inMap["name"]; !ok {
|
||||
continue
|
||||
}
|
||||
name, ok := inMap["name"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// The only case we need to force the namespace
|
||||
// if for the "service account". "default" is
|
||||
// kind of hardcoded here for right now.
|
||||
if name != "default" {
|
||||
continue
|
||||
}
|
||||
inMap["namespace"] = p.Namespace
|
||||
l[idx] = inMap
|
||||
default:
|
||||
// nothing to do for right now
|
||||
}
|
||||
}
|
||||
return in, nil
|
||||
case map[string]interface{}:
|
||||
// Will happen if the createField=true
|
||||
// when the namespace is added to the
|
||||
// object
|
||||
inMap := in.(map[string]interface{})
|
||||
if len(inMap) == 0 {
|
||||
return p.Namespace, nil
|
||||
} else {
|
||||
return in, nil
|
||||
}
|
||||
default:
|
||||
return in, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewNamespaceTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &NamespaceTransformerPlugin{}
|
||||
}
|
||||
|
||||
@@ -8,20 +8,19 @@ import (
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/filters/patchjson6902"
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type PatchJson6902TransformerPlugin struct {
|
||||
ldr ifc.Loader
|
||||
decodedPatch jsonpatch.Patch
|
||||
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
JsonOp string `json:"jsonOp,omitempty" yaml:"jsonOp,omitempty"`
|
||||
Target types.PatchTarget `json:"target,omitempty" yaml:"target,omitempty"`
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
JsonOp string `json:"jsonOp,omitempty" yaml:"jsonOp,omitempty"`
|
||||
}
|
||||
|
||||
func (p *PatchJson6902TransformerPlugin) Config(
|
||||
@@ -71,33 +70,29 @@ func (p *PatchJson6902TransformerPlugin) Config(
|
||||
}
|
||||
|
||||
func (p *PatchJson6902TransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
if p.Target == nil {
|
||||
return fmt.Errorf("must specify a target for patch %s", p.JsonOp)
|
||||
}
|
||||
resources, err := m.Select(*p.Target)
|
||||
id := resid.NewResIdWithNamespace(
|
||||
resid.Gvk{
|
||||
Group: p.Target.Group,
|
||||
Version: p.Target.Version,
|
||||
Kind: p.Target.Kind,
|
||||
},
|
||||
p.Target.Name,
|
||||
p.Target.Namespace,
|
||||
)
|
||||
obj, err := m.GetById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, res := range resources {
|
||||
internalAnnotations := kioutil.GetInternalAnnotations(&res.RNode)
|
||||
|
||||
err = res.ApplyFilter(patchjson6902.Filter{
|
||||
Patch: p.JsonOp,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
annotations := res.GetAnnotations()
|
||||
for key, value := range internalAnnotations {
|
||||
annotations[key] = value
|
||||
}
|
||||
err = res.SetAnnotations(annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rawObj, err := obj.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
modifiedObj, err := p.decodedPatch.Apply(rawObj)
|
||||
if err != nil {
|
||||
return errors.Wrapf(
|
||||
err, "failed to apply json patch '%s'", p.JsonOp)
|
||||
}
|
||||
return obj.UnmarshalJSON(modifiedObj)
|
||||
}
|
||||
|
||||
func NewPatchJson6902TransformerPlugin() resmap.TransformerPlugin {
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type PatchStrategicMergeTransformerPlugin struct {
|
||||
h *resmap.PluginHelpers
|
||||
loadedPatches []*resource.Resource
|
||||
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
|
||||
Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
|
||||
@@ -20,6 +21,7 @@ type PatchStrategicMergeTransformerPlugin struct {
|
||||
|
||||
func (p *PatchStrategicMergeTransformerPlugin) Config(
|
||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.h = h
|
||||
err = yaml.Unmarshal(c, p)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -28,58 +30,57 @@ func (p *PatchStrategicMergeTransformerPlugin) Config(
|
||||
return fmt.Errorf("empty file path and empty patch content")
|
||||
}
|
||||
if len(p.Paths) != 0 {
|
||||
patches, err := loadFromPaths(h, p.Paths)
|
||||
if err != nil {
|
||||
return err
|
||||
for _, onePath := range p.Paths {
|
||||
res, err := p.h.ResmapFactory().RF().SliceFromBytes([]byte(onePath))
|
||||
if err == nil {
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
continue
|
||||
}
|
||||
res, err = p.h.ResmapFactory().RF().SliceFromPatches(
|
||||
p.h.Loader(), []types.PatchStrategicMerge{onePath})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
}
|
||||
p.loadedPatches = append(p.loadedPatches, patches...)
|
||||
}
|
||||
if p.Patches != "" {
|
||||
patches, err := h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
|
||||
res, err := p.h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.loadedPatches = append(p.loadedPatches, patches...)
|
||||
p.loadedPatches = append(p.loadedPatches, res...)
|
||||
}
|
||||
|
||||
if len(p.loadedPatches) == 0 {
|
||||
return fmt.Errorf(
|
||||
"patch appears to be empty; files=%v, Patch=%s", p.Paths, p.Patches)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadFromPaths(
|
||||
h *resmap.PluginHelpers,
|
||||
paths []types.PatchStrategicMerge) (
|
||||
result []*resource.Resource, err error) {
|
||||
var patches []*resource.Resource
|
||||
for _, path := range paths {
|
||||
// For legacy reasons, attempt to treat the path string as
|
||||
// actual patch content.
|
||||
patches, err = h.ResmapFactory().RF().SliceFromBytes([]byte(path))
|
||||
if err != nil {
|
||||
// Failing that, treat it as a file path.
|
||||
patches, err = h.ResmapFactory().RF().SliceFromPatches(
|
||||
h.Loader(), []types.PatchStrategicMerge{path})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
result = append(result, patches...)
|
||||
}
|
||||
return
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
for _, patch := range p.loadedPatches {
|
||||
patches, err := p.h.ResmapFactory().MergePatches(p.loadedPatches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, patch := range patches.Resources() {
|
||||
target, err := m.GetById(patch.OrgId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = m.ApplySmPatch(
|
||||
resource.MakeIdSet([]*resource.Resource{target}), patch); err != nil {
|
||||
err = target.Patch(patch.Kunstructured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// remove the resource from resmap
|
||||
// when the patch is to $patch: delete that target
|
||||
if len(target.Map()) == 0 {
|
||||
err = m.Remove(target.CurId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,14 +5,12 @@ package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"sigs.k8s.io/kustomize/api/filters/patchjson6902"
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -22,109 +20,103 @@ type PatchTransformerPlugin struct {
|
||||
Path string `json:"path,omitempty" yaml:"path,omitempty"`
|
||||
Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
|
||||
Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
|
||||
Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"`
|
||||
}
|
||||
|
||||
func (p *PatchTransformerPlugin) Config(
|
||||
h *resmap.PluginHelpers, c []byte) error {
|
||||
err := yaml.Unmarshal(c, p)
|
||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
||||
err = yaml.Unmarshal(c, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Patch = strings.TrimSpace(p.Patch)
|
||||
if p.Patch == "" && p.Path == "" {
|
||||
return fmt.Errorf(
|
||||
err = fmt.Errorf(
|
||||
"must specify one of patch and path in\n%s", string(c))
|
||||
return
|
||||
}
|
||||
if p.Patch != "" && p.Path != "" {
|
||||
return fmt.Errorf(
|
||||
err = fmt.Errorf(
|
||||
"patch and path can't be set at the same time\n%s", string(c))
|
||||
return
|
||||
}
|
||||
var in []byte
|
||||
if p.Path != "" {
|
||||
loaded, loadErr := h.Loader().Load(p.Path)
|
||||
if loadErr != nil {
|
||||
return loadErr
|
||||
in, err = h.Loader().Load(p.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p.Patch = string(loaded)
|
||||
}
|
||||
if p.Patch != "" {
|
||||
in = []byte(p.Patch)
|
||||
}
|
||||
|
||||
patchSM, errSM := h.ResmapFactory().RF().FromBytes([]byte(p.Patch))
|
||||
patchJson, errJson := jsonPatchFromBytes([]byte(p.Patch))
|
||||
if (errSM == nil && errJson == nil) ||
|
||||
(patchSM != nil && patchJson != nil) {
|
||||
return fmt.Errorf(
|
||||
"illegally qualifies as both an SM and JSON patch: [%v]",
|
||||
p.Patch)
|
||||
}
|
||||
patchSM, errSM := h.ResmapFactory().RF().FromBytes(in)
|
||||
patchJson, errJson := jsonPatchFromBytes(in)
|
||||
if errSM != nil && errJson != nil {
|
||||
return fmt.Errorf(
|
||||
"unable to parse SM or JSON patch from [%v]", p.Patch)
|
||||
err = fmt.Errorf(
|
||||
"unable to get either a Strategic Merge Patch or JSON patch 6902 from %s", p.Patch)
|
||||
return
|
||||
}
|
||||
if errSM == nil {
|
||||
if errSM == nil && errJson != nil {
|
||||
p.loadedPatch = patchSM
|
||||
if p.Options["allowNameChange"] {
|
||||
p.loadedPatch.AllowNameChange()
|
||||
}
|
||||
if p.Options["allowKindChange"] {
|
||||
p.loadedPatch.AllowKindChange()
|
||||
}
|
||||
} else {
|
||||
}
|
||||
if errJson == nil && errSM != nil {
|
||||
p.decodedPatch = patchJson
|
||||
}
|
||||
if patchSM != nil && patchJson != nil {
|
||||
err = fmt.Errorf(
|
||||
"a patch can't be both a Strategic Merge Patch and JSON patch 6902 %s", p.Patch)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PatchTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
if p.loadedPatch == nil {
|
||||
return p.transformJson6902(m, p.decodedPatch)
|
||||
}
|
||||
// The patch was a strategic merge patch
|
||||
return p.transformStrategicMerge(m, p.loadedPatch)
|
||||
}
|
||||
|
||||
// transformStrategicMerge applies the provided strategic merge patch
|
||||
// to all the resources in the ResMap that match either the Target or
|
||||
// the identifier of the patch.
|
||||
func (p *PatchTransformerPlugin) transformStrategicMerge(m resmap.ResMap, patch *resource.Resource) error {
|
||||
if p.Target == nil {
|
||||
target, err := m.GetById(patch.OrgId())
|
||||
if p.loadedPatch != nil && p.Target == nil {
|
||||
target, err := m.GetById(p.loadedPatch.OrgId())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return target.ApplySmPatch(patch)
|
||||
err = target.Patch(p.loadedPatch.Kunstructured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
selected, err := m.Select(*p.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return m.ApplySmPatch(resource.MakeIdSet(selected), patch)
|
||||
}
|
||||
|
||||
// transformJson6902 applies the provided json6902 patch
|
||||
// to all the resources in the ResMap that match the Target.
|
||||
func (p *PatchTransformerPlugin) transformJson6902(m resmap.ResMap, patch jsonpatch.Patch) error {
|
||||
if p.Target == nil {
|
||||
return fmt.Errorf("must specify a target for patch %s", p.Patch)
|
||||
}
|
||||
|
||||
resources, err := m.Select(*p.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, res := range resources {
|
||||
res.StorePreviousId()
|
||||
internalAnnotations := kioutil.GetInternalAnnotations(&res.RNode)
|
||||
err = res.ApplyFilter(patchjson6902.Filter{
|
||||
Patch: p.Patch,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
if p.decodedPatch != nil {
|
||||
rawObj, err := res.MarshalJSON()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
modifiedObj, err := p.decodedPatch.Apply(rawObj)
|
||||
if err != nil {
|
||||
return errors.Wrapf(
|
||||
err, "failed to apply json patch '%s'", p.Patch)
|
||||
}
|
||||
err = res.UnmarshalJSON(modifiedObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
annotations := res.GetAnnotations()
|
||||
for key, value := range internalAnnotations {
|
||||
annotations[key] = value
|
||||
if p.loadedPatch != nil {
|
||||
patchCopy := p.loadedPatch.DeepCopy()
|
||||
patchCopy.SetName(res.GetName())
|
||||
patchCopy.SetNamespace(res.GetNamespace())
|
||||
patchCopy.SetGvk(res.GetGvk())
|
||||
err = res.Patch(patchCopy.Kunstructured)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = res.SetAnnotations(annotations)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,27 +5,31 @@ package builtins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// Add the given prefix and suffix to the field.
|
||||
type PrefixSuffixTransformerPlugin struct {
|
||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
||||
FieldSpecs types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
}
|
||||
|
||||
// A Gvk skip list for prefix/suffix modification.
|
||||
// hard coded for now - eventually should be part of config.
|
||||
var prefixSuffixFieldSpecsToSkip = types.FsSlice{
|
||||
{Gvk: resid.Gvk{Kind: "CustomResourceDefinition"}},
|
||||
{Gvk: resid.Gvk{Group: "apiregistration.k8s.io", Kind: "APIService"}},
|
||||
{Gvk: resid.Gvk{Kind: "Namespace"}},
|
||||
// Not placed in a file yet due to lack of demand.
|
||||
var prefixSuffixFieldSpecsToSkip = []types.FieldSpec{
|
||||
{
|
||||
Gvk: resid.Gvk{Kind: "CustomResourceDefinition"},
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{Group: "apiregistration.k8s.io", Kind: "APIService"},
|
||||
},
|
||||
}
|
||||
|
||||
func (p *PrefixSuffixTransformerPlugin) Config(
|
||||
@@ -44,40 +48,46 @@ func (p *PrefixSuffixTransformerPlugin) Config(
|
||||
}
|
||||
|
||||
func (p *PrefixSuffixTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
|
||||
// Even if both the Prefix and Suffix are empty we want
|
||||
// to proceed with the transformation. This allows to add contextual
|
||||
// information to the resources (AddNamePrefix and AddNameSuffix).
|
||||
|
||||
for _, r := range m.Resources() {
|
||||
// TODO: move this test into the filter (i.e. make a better filter)
|
||||
if p.shouldSkip(r.OrgId()) {
|
||||
// Don't change the actual definition
|
||||
// of a CRD.
|
||||
continue
|
||||
}
|
||||
id := r.OrgId()
|
||||
// current default configuration contains
|
||||
// only one entry: "metadata/name" with no GVK
|
||||
for _, fs := range p.FieldSpecs {
|
||||
// TODO: this is redundant to filter (but needed for now)
|
||||
if !id.IsSelected(&fs.Gvk) {
|
||||
for _, path := range p.FieldSpecs {
|
||||
if !id.IsSelected(&path.Gvk) {
|
||||
// With the currrent default configuration,
|
||||
// because no Gvk is specified, so a wild
|
||||
// card
|
||||
continue
|
||||
}
|
||||
// TODO: move this test into the filter.
|
||||
if smellsLikeANameChange(&fs) {
|
||||
|
||||
if smellsLikeANameChange(&path) {
|
||||
// "metadata/name" is the only field.
|
||||
// this will add a prefix and a suffix
|
||||
// to the resource even if those are
|
||||
// empty
|
||||
|
||||
r.AddNamePrefix(p.Prefix)
|
||||
r.AddNameSuffix(p.Suffix)
|
||||
if p.Prefix != "" || p.Suffix != "" {
|
||||
r.StorePreviousId()
|
||||
}
|
||||
}
|
||||
if err := r.ApplyFilter(prefixsuffix.Filter{
|
||||
Prefix: p.Prefix,
|
||||
Suffix: p.Suffix,
|
||||
FieldSpec: fs,
|
||||
}); err != nil {
|
||||
|
||||
// the addPrefixSuffix method will not
|
||||
// change the name if both the prefix and suffix
|
||||
// are empty.
|
||||
err := transform.MutateField(
|
||||
r.Map(),
|
||||
path.PathSlice(),
|
||||
path.CreateIfNotPresent,
|
||||
p.addPrefixSuffix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -89,7 +99,8 @@ func smellsLikeANameChange(fs *types.FieldSpec) bool {
|
||||
return fs.Path == "metadata/name"
|
||||
}
|
||||
|
||||
func (p *PrefixSuffixTransformerPlugin) shouldSkip(id resid.ResId) bool {
|
||||
func (p *PrefixSuffixTransformerPlugin) shouldSkip(
|
||||
id resid.ResId) bool {
|
||||
for _, path := range prefixSuffixFieldSpecsToSkip {
|
||||
if id.IsSelected(&path.Gvk) {
|
||||
return true
|
||||
@@ -98,6 +109,15 @@ func (p *PrefixSuffixTransformerPlugin) shouldSkip(id resid.ResId) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *PrefixSuffixTransformerPlugin) addPrefixSuffix(
|
||||
in interface{}) (interface{}, error) {
|
||||
s, ok := in.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, s)
|
||||
}
|
||||
return fmt.Sprintf("%s%s%s", p.Prefix, s, p.Suffix), nil
|
||||
}
|
||||
|
||||
func NewPrefixSuffixTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &PrefixSuffixTransformerPlugin{}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
// Code generated by pluginator on ReplacementTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/replacement"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// Replace values in targets with values from a source
|
||||
type ReplacementTransformerPlugin struct {
|
||||
ReplacementList []types.ReplacementField `json:"replacements,omitempty" yaml:"replacements,omitempty"`
|
||||
Replacements []types.Replacement `json:"omitempty" yaml:"omitempty"`
|
||||
}
|
||||
|
||||
func (p *ReplacementTransformerPlugin) Config(
|
||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
||||
p.ReplacementList = []types.ReplacementField{}
|
||||
if err := yaml.Unmarshal(c, p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, r := range p.ReplacementList {
|
||||
if r.Path != "" && (r.Source != nil || len(r.Targets) != 0) {
|
||||
return fmt.Errorf("cannot specify both path and inline replacement")
|
||||
}
|
||||
if r.Path != "" {
|
||||
// load the replacement from the path
|
||||
content, err := h.Loader().Load(r.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repl := types.Replacement{}
|
||||
if err := yaml.Unmarshal(content, &repl); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Replacements = append(p.Replacements, repl)
|
||||
} else {
|
||||
// replacement information is already loaded
|
||||
p.Replacements = append(p.Replacements, r.Replacement)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ReplacementTransformerPlugin) Transform(m resmap.ResMap) (err error) {
|
||||
return m.ApplyFilter(replacement.Filter{
|
||||
Replacements: p.Replacements,
|
||||
})
|
||||
}
|
||||
|
||||
func NewReplacementTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &ReplacementTransformerPlugin{}
|
||||
}
|
||||
@@ -6,10 +6,11 @@ package builtins
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/replicacount"
|
||||
"sigs.k8s.io/kustomize/api/transform"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
@@ -29,22 +30,18 @@ func (p *ReplicaCountTransformerPlugin) Config(
|
||||
|
||||
func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
found := false
|
||||
for _, fs := range p.FieldSpecs {
|
||||
matcher := p.createMatcher(fs)
|
||||
resList := m.GetMatchingResourcesByAnyId(matcher)
|
||||
if len(resList) > 0 {
|
||||
for i, replicaSpec := range p.FieldSpecs {
|
||||
matcher := p.createMatcher(i)
|
||||
matchOriginal := m.GetMatchingResourcesByOriginalId(matcher)
|
||||
matchCurrent := m.GetMatchingResourcesByCurrentId(matcher)
|
||||
|
||||
for _, res := range append(matchOriginal, matchCurrent...) {
|
||||
found = true
|
||||
for _, r := range resList {
|
||||
// There are redundant checks in the filter
|
||||
// that we'll live with until resolution of
|
||||
// https://github.com/kubernetes-sigs/kustomize/issues/2506
|
||||
err := r.ApplyFilter(replicacount.Filter{
|
||||
Replica: p.Replica,
|
||||
FieldSpec: fs,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err := transform.MutateField(
|
||||
res.Map(), replicaSpec.PathSlice(),
|
||||
replicaSpec.CreateIfNotPresent, p.addReplicas)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,12 +59,30 @@ func (p *ReplicaCountTransformerPlugin) Transform(m resmap.ResMap) error {
|
||||
}
|
||||
|
||||
// Match Replica.Name and FieldSpec
|
||||
func (p *ReplicaCountTransformerPlugin) createMatcher(fs types.FieldSpec) resmap.IdMatcher {
|
||||
func (p *ReplicaCountTransformerPlugin) createMatcher(i int) resmap.IdMatcher {
|
||||
return func(r resid.ResId) bool {
|
||||
return r.Name == p.Replica.Name && r.Gvk.IsSelected(&fs.Gvk)
|
||||
return r.Name == p.Replica.Name &&
|
||||
r.Gvk.IsSelected(&p.FieldSpecs[i].Gvk)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ReplicaCountTransformerPlugin) addReplicas(in interface{}) (interface{}, error) {
|
||||
switch m := in.(type) {
|
||||
case int64:
|
||||
// Was already in the field.
|
||||
case map[string]interface{}:
|
||||
if len(m) != 0 {
|
||||
// A map was already in the replicas field, don't want to
|
||||
// discard this data silently.
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, m)
|
||||
}
|
||||
// Just got added, default type is map, but we can return anything.
|
||||
default:
|
||||
return nil, fmt.Errorf("%#v is expected to be %T", in, m)
|
||||
}
|
||||
return p.Replica.Count, nil
|
||||
}
|
||||
|
||||
func NewReplicaCountTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &ReplicaCountTransformerPlugin{}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,12 @@ import (
|
||||
type SecretGeneratorPlugin struct {
|
||||
h *resmap.PluginHelpers
|
||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
types.GeneratorOptions
|
||||
types.SecretArgs
|
||||
}
|
||||
|
||||
func (p *SecretGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
|
||||
p.GeneratorOptions = types.GeneratorOptions{}
|
||||
p.SecretArgs = types.SecretArgs{}
|
||||
err = yaml.Unmarshal(config, p)
|
||||
if p.SecretArgs.Name == "" {
|
||||
@@ -31,7 +33,8 @@ func (p *SecretGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (
|
||||
|
||||
func (p *SecretGeneratorPlugin) Generate() (resmap.ResMap, error) {
|
||||
return p.h.ResmapFactory().FromSecretArgs(
|
||||
kv.NewLoader(p.h.Loader(), p.h.Validator()), p.SecretArgs)
|
||||
kv.NewLoader(p.h.Loader(), p.h.Validator()),
|
||||
&p.GeneratorOptions, p.SecretArgs)
|
||||
}
|
||||
|
||||
func NewSecretGeneratorPlugin() resmap.GeneratorPlugin {
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
// Code generated by pluginator on ValueAddTransformer; DO NOT EDIT.
|
||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
||||
|
||||
package builtins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/namespace"
|
||||
"sigs.k8s.io/kustomize/api/filters/valueadd"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// An 'Add' transformer inspired by the IETF RFC 6902 JSON spec Add operation.
|
||||
type ValueAddTransformerPlugin struct {
|
||||
// Value is the value to add.
|
||||
// Defaults to base name of encompassing kustomization root.
|
||||
Value string `json:"value,omitempty" yaml:"value,omitempty"`
|
||||
|
||||
// Targets is a slice of targets that should have the value added.
|
||||
Targets []Target `json:"targets,omitempty" yaml:"targets,omitempty"`
|
||||
|
||||
// TargetFilePath is a file path. If specified, the file will be parsed into
|
||||
// a slice of Target, and appended to anything that was specified in the
|
||||
// Targets field. This is just a means to share common target specifications.
|
||||
TargetFilePath string `json:"targetFilePath,omitempty" yaml:"targetFilePath,omitempty"`
|
||||
}
|
||||
|
||||
// Target describes where to put the value.
|
||||
type Target struct {
|
||||
// Selector selects the resources to modify.
|
||||
Selector *types.Selector `json:"selector,omitempty" yaml:"selector,omitempty"`
|
||||
|
||||
// NotSelector selects the resources to exclude
|
||||
// from those included by overly broad selectors.
|
||||
// TODO: implement this?
|
||||
// NotSelector *types.Selector `json:"notSelector,omitempty" yaml:"notSelector,omitempty"`
|
||||
|
||||
// FieldPath is a JSON-style path to the field intended to hold the value.
|
||||
FieldPath string `json:"fieldPath,omitempty" yaml:"fieldPath,omitempty"`
|
||||
|
||||
// FilePathPosition is passed to the filter directly. Look there for doc.
|
||||
FilePathPosition int `json:"filePathPosition,omitempty" yaml:"filePathPosition,omitempty"`
|
||||
}
|
||||
|
||||
func (p *ValueAddTransformerPlugin) Config(h *resmap.PluginHelpers, c []byte) error {
|
||||
err := yaml.Unmarshal(c, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Value = strings.TrimSpace(p.Value)
|
||||
if p.Value == "" {
|
||||
p.Value = filepath.Base(h.Loader().Root())
|
||||
}
|
||||
if p.TargetFilePath != "" {
|
||||
bytes, err := h.Loader().Load(p.TargetFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var targets struct {
|
||||
Targets []Target `json:"targets,omitempty" yaml:"targets,omitempty"`
|
||||
}
|
||||
err = yaml.Unmarshal(bytes, &targets)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Targets = append(p.Targets, targets.Targets...)
|
||||
}
|
||||
if len(p.Targets) == 0 {
|
||||
return fmt.Errorf("must specify at least one target")
|
||||
}
|
||||
for _, target := range p.Targets {
|
||||
if err = validateSelector(target.Selector); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: call validateSelector(target.NotSelector) if field added.
|
||||
if err = validateJsonFieldPath(target.FieldPath); err != nil {
|
||||
return err
|
||||
}
|
||||
if target.FilePathPosition < 0 {
|
||||
return fmt.Errorf(
|
||||
"value of FilePathPosition (%d) cannot be negative",
|
||||
target.FilePathPosition)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: implement
|
||||
func validateSelector(_ *types.Selector) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Enforce RFC 6902?
|
||||
func validateJsonFieldPath(p string) error {
|
||||
if len(p) == 0 {
|
||||
return fmt.Errorf("fieldPath cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ValueAddTransformerPlugin) Transform(m resmap.ResMap) (err error) {
|
||||
for _, t := range p.Targets {
|
||||
var resources []*resource.Resource
|
||||
if t.Selector == nil {
|
||||
resources = m.Resources()
|
||||
} else {
|
||||
resources, err = m.Select(*t.Selector)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// TODO: consider t.NotSelector if implemented
|
||||
for _, res := range resources {
|
||||
if t.FieldPath == types.MetadataNamespacePath {
|
||||
err = res.ApplyFilter(namespace.Filter{
|
||||
Namespace: p.Value,
|
||||
})
|
||||
} else {
|
||||
err = res.ApplyFilter(valueadd.Filter{
|
||||
Value: p.Value,
|
||||
FieldPath: t.FieldPath,
|
||||
FilePathPosition: t.FilePathPosition,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewValueAddTransformerPlugin() resmap.TransformerPlugin {
|
||||
return &ValueAddTransformerPlugin{}
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package filesys
|
||||
package filesys_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/filesys"
|
||||
)
|
||||
|
||||
func TestJoin(t *testing.T) {
|
||||
@@ -100,7 +98,6 @@ func TestNewTempConfirmDir(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(string(tmp))
|
||||
|
||||
delinked, err := filepath.EvalSymlinks(string(tmp))
|
||||
if err != nil {
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package filesys provides a file system abstraction,
|
||||
// a subset of that provided by golang.org/pkg/os,
|
||||
// with an on-disk and in-memory representation.
|
||||
//
|
||||
// Deprecated: use sigs.k8s.io/kustomize/kyaml/filesys instead.
|
||||
package filesys
|
||||
|
||||
import "sigs.k8s.io/kustomize/kyaml/filesys"
|
||||
|
||||
const (
|
||||
// Separator is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.Separator.
|
||||
Separator = filesys.Separator
|
||||
// SelfDir is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.SelfDir.
|
||||
SelfDir = filesys.SelfDir
|
||||
// ParentDir is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.ParentDir.
|
||||
ParentDir = filesys.ParentDir
|
||||
)
|
||||
|
||||
type (
|
||||
// FileSystem is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.FileSystem.
|
||||
FileSystem = filesys.FileSystem
|
||||
// FileSystemOrOnDisk is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.FileSystemOrOnDisk.
|
||||
FileSystemOrOnDisk = filesys.FileSystemOrOnDisk
|
||||
// ConfirmedDir is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.ConfirmedDir.
|
||||
ConfirmedDir = filesys.ConfirmedDir
|
||||
)
|
||||
|
||||
// MakeEmptyDirInMemory is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.MakeEmptyDirInMemory.
|
||||
func MakeEmptyDirInMemory() FileSystem { return filesys.MakeEmptyDirInMemory() }
|
||||
|
||||
// MakeFsInMemory is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.MakeFsInMemory.
|
||||
func MakeFsInMemory() FileSystem { return filesys.MakeFsInMemory() }
|
||||
|
||||
// MakeFsOnDisk is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.MakeFsOnDisk.
|
||||
func MakeFsOnDisk() FileSystem { return filesys.MakeFsOnDisk() }
|
||||
|
||||
// NewTmpConfirmedDir is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.NewTmpConfirmedDir.
|
||||
func NewTmpConfirmedDir() (filesys.ConfirmedDir, error) { return filesys.NewTmpConfirmedDir() }
|
||||
|
||||
// RootedPath is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.RootedPath.
|
||||
func RootedPath(elem ...string) string { return filesys.RootedPath(elem...) }
|
||||
|
||||
// StripTrailingSeps is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.StripTrailingSeps.
|
||||
func StripTrailingSeps(s string) string { return filesys.StripTrailingSeps(s) }
|
||||
|
||||
// StripLeadingSeps is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.StripLeadingSeps.
|
||||
func StripLeadingSeps(s string) string { return filesys.StripLeadingSeps(s) }
|
||||
|
||||
// PathSplit is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.PathSplit.
|
||||
func PathSplit(incoming string) []string { return filesys.PathSplit(incoming) }
|
||||
|
||||
// PathJoin is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.PathJoin.
|
||||
func PathJoin(incoming []string) string { return filesys.PathJoin(incoming) }
|
||||
|
||||
// InsertPathPart is deprecated, use sigs.k8s.io/kustomize/kyaml/filesys.InsertPathPart.
|
||||
func InsertPathPart(path string, pos int, part string) string {
|
||||
return filesys.InsertPathPart(path, pos, part)
|
||||
}
|
||||
50
api/filesys/filesystem.go
Normal file
50
api/filesys/filesystem.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package filesys provides a file system abstraction layer.
|
||||
package filesys
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const (
|
||||
Separator = string(filepath.Separator)
|
||||
SelfDir = "."
|
||||
ParentDir = ".."
|
||||
)
|
||||
|
||||
// FileSystem groups basic os filesystem methods.
|
||||
// It's supposed be functional subset of https://golang.org/pkg/os
|
||||
type FileSystem interface {
|
||||
// Create a file.
|
||||
Create(path string) (File, error)
|
||||
// MkDir makes a directory.
|
||||
Mkdir(path string) error
|
||||
// MkDirAll makes a directory path, creating intervening directories.
|
||||
MkdirAll(path string) error
|
||||
// RemoveAll removes path and any children it contains.
|
||||
RemoveAll(path string) error
|
||||
// Open opens the named file for reading.
|
||||
Open(path string) (File, error)
|
||||
// IsDir returns true if the path is a directory.
|
||||
IsDir(path string) bool
|
||||
// CleanedAbs converts the given path into a
|
||||
// directory and a file name, where the directory
|
||||
// is represented as a ConfirmedDir and all that implies.
|
||||
// If the entire path is a directory, the file component
|
||||
// is an empty string.
|
||||
CleanedAbs(path string) (ConfirmedDir, string, error)
|
||||
// Exists is true if the path exists in the file system.
|
||||
Exists(path string) bool
|
||||
// Glob returns the list of matching files,
|
||||
// emulating https://golang.org/pkg/path/filepath/#Glob
|
||||
Glob(pattern string) ([]string, error)
|
||||
// ReadFile returns the contents of the file at the given path.
|
||||
ReadFile(path string) ([]byte, error)
|
||||
// WriteFile writes the data to a file at the given path,
|
||||
// overwriting anything that's already there.
|
||||
WriteFile(path string, data []byte) error
|
||||
// Walk walks the file system with the given WalkFunc.
|
||||
Walk(path string, walkFn filepath.WalkFunc) error
|
||||
}
|
||||
@@ -6,7 +6,6 @@ package filesys
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -38,9 +37,9 @@ type fsNode struct {
|
||||
// if this node is a file, this is the content.
|
||||
content []byte
|
||||
|
||||
// if offset is not nil the file is open and it tracks
|
||||
// the current file offset.
|
||||
offset *int
|
||||
// if this node is a file, this tracks whether or
|
||||
// not it is "open".
|
||||
open bool
|
||||
}
|
||||
|
||||
// MakeEmptyDirInMemory returns an empty directory.
|
||||
@@ -120,14 +119,11 @@ func (n *fsNode) addFile(name string, c []byte) (result *fsNode, err error) {
|
||||
result, ok := parent.dir[fileName]
|
||||
if ok {
|
||||
// File already exists; overwrite it.
|
||||
if result.offset != nil {
|
||||
return nil, fmt.Errorf("cannot add already opened file '%s'", n.Path())
|
||||
}
|
||||
result.content = append(result.content[:0], c...)
|
||||
result.content = c
|
||||
return result, nil
|
||||
}
|
||||
result = &fsNode{
|
||||
content: append([]byte(nil), c...),
|
||||
content: c,
|
||||
parent: parent,
|
||||
}
|
||||
parent.dir[fileName] = result
|
||||
@@ -137,12 +133,7 @@ func (n *fsNode) addFile(name string, c []byte) (result *fsNode, err error) {
|
||||
// Create implements FileSystem.
|
||||
// Create makes an empty file.
|
||||
func (n *fsNode) Create(path string) (result File, err error) {
|
||||
f, err := n.AddFile(path, nil)
|
||||
if err != nil {
|
||||
return f, err
|
||||
}
|
||||
f.offset = new(int)
|
||||
return f, nil
|
||||
return n.AddFile(path, []byte{})
|
||||
}
|
||||
|
||||
// WriteFile implements FileSystem.
|
||||
@@ -163,7 +154,6 @@ func (n *fsNode) AddFile(
|
||||
}
|
||||
|
||||
func (n *fsNode) addDir(path string) (result *fsNode, err error) {
|
||||
|
||||
parent := n
|
||||
dName, subDirName := mySplit(path)
|
||||
if dName != "" {
|
||||
@@ -236,7 +226,7 @@ func (n *fsNode) CleanedAbs(path string) (ConfirmedDir, string, error) {
|
||||
return "", "", errors.Wrap(err, "unable to clean")
|
||||
}
|
||||
if node == nil {
|
||||
return "", "", notExistError(path)
|
||||
return "", "", fmt.Errorf("'%s' doesn't exist", path)
|
||||
}
|
||||
if node.isNodeADir() {
|
||||
return ConfirmedDir(node.Path()), "", nil
|
||||
@@ -309,8 +299,7 @@ func (n *fsNode) RemoveAll(path string) error {
|
||||
return err
|
||||
}
|
||||
if result == nil {
|
||||
// If the path doesn't exist, no need to remove anything.
|
||||
return nil
|
||||
return fmt.Errorf("cannot find '%s' to remove it", path)
|
||||
}
|
||||
return result.Remove()
|
||||
}
|
||||
@@ -350,32 +339,6 @@ func (n *fsNode) IsDir(path string) bool {
|
||||
return result.isNodeADir()
|
||||
}
|
||||
|
||||
// ReadDir implements FileSystem.
|
||||
func (n *fsNode) ReadDir(path string) ([]string, error) {
|
||||
if !n.Exists(path) {
|
||||
return nil, notExistError(path)
|
||||
}
|
||||
if !n.IsDir(path) {
|
||||
return nil, fmt.Errorf("%s is not a directory", path)
|
||||
}
|
||||
|
||||
dir, err := n.Find(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dir == nil {
|
||||
return nil, fmt.Errorf("could not find directory %s", path)
|
||||
}
|
||||
|
||||
keys := make([]string, len(dir.dir))
|
||||
i := 0
|
||||
for k := range dir.dir {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
// Size returns the size of the node.
|
||||
func (n *fsNode) Size() int64 {
|
||||
if n.isNodeADir() {
|
||||
@@ -385,38 +348,22 @@ func (n *fsNode) Size() int64 {
|
||||
}
|
||||
|
||||
// Open implements FileSystem.
|
||||
// Open opens the node in read-write mode and sets the offset its start.
|
||||
// Writing right after opening the file will replace the original content
|
||||
// and move the offset forward, as with a file opened with O_RDWR | O_CREATE.
|
||||
//
|
||||
// As an example, let's consider a file with content "content":
|
||||
// - open: sets offset to start, content is "content"
|
||||
// - write "@": offset increases by one, the content is now "@ontent"
|
||||
// - read the rest: since offset is 1, the read operation returns "ontent"
|
||||
// - write "$": offset is at EOF, so "$" is appended and content is now "@ontent$"
|
||||
// - read the rest: returns 0 bytes and EOF
|
||||
// - close: the content is still "@ontent$"
|
||||
// Open opens the node for reading (just marks it).
|
||||
func (n *fsNode) Open(path string) (File, error) {
|
||||
result, err := n.Find(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result == nil {
|
||||
return nil, notExistError(path)
|
||||
return nil, fmt.Errorf("cannot find '%s' to open it", path)
|
||||
}
|
||||
if result.offset != nil {
|
||||
return nil, fmt.Errorf("cannot open previously opened file '%s'", path)
|
||||
}
|
||||
result.offset = new(int)
|
||||
result.open = true
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Close marks the node closed.
|
||||
func (n *fsNode) Close() error {
|
||||
if n.offset == nil {
|
||||
return fmt.Errorf("cannot close already closed file '%s'", n.Path())
|
||||
}
|
||||
n.offset = nil
|
||||
n.open = false
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -427,14 +374,11 @@ func (n *fsNode) ReadFile(path string) (c []byte, err error) {
|
||||
return nil, err
|
||||
}
|
||||
if result == nil {
|
||||
return nil, notExistError(path)
|
||||
}
|
||||
if result.isNodeADir() {
|
||||
return nil, fmt.Errorf("cannot read content from non-file '%s'", n.Path())
|
||||
return nil, fmt.Errorf("cannot find '%s' to read it", path)
|
||||
}
|
||||
c = make([]byte, len(result.content))
|
||||
copy(c, result.content)
|
||||
return c, nil
|
||||
_, err = result.Read(c)
|
||||
return c, err
|
||||
}
|
||||
|
||||
// Read returns the content of the file node.
|
||||
@@ -443,19 +387,7 @@ func (n *fsNode) Read(d []byte) (c int, err error) {
|
||||
return 0, fmt.Errorf(
|
||||
"cannot read content from non-file '%s'", n.Path())
|
||||
}
|
||||
if n.offset == nil {
|
||||
return 0, fmt.Errorf("cannot read from closed file '%s'", n.Path())
|
||||
}
|
||||
|
||||
rest := n.content[*n.offset:]
|
||||
if len(d) < len(rest) {
|
||||
rest = rest[:len(d)]
|
||||
} else {
|
||||
err = io.EOF
|
||||
}
|
||||
copy(d, rest)
|
||||
*n.offset += len(rest)
|
||||
return len(rest), err
|
||||
return copy(d, n.content), nil
|
||||
}
|
||||
|
||||
// Write saves the contents of the argument to the file node.
|
||||
@@ -464,12 +396,8 @@ func (n *fsNode) Write(p []byte) (c int, err error) {
|
||||
return 0, fmt.Errorf(
|
||||
"cannot write content to non-file '%s'", n.Path())
|
||||
}
|
||||
if n.offset == nil {
|
||||
return 0, fmt.Errorf("cannot write to closed file '%s'", n.Path())
|
||||
}
|
||||
n.content = append(n.content[:*n.offset], p...)
|
||||
*n.offset = len(n.content)
|
||||
return len(p), nil
|
||||
n.content = make([]byte, len(p))
|
||||
return copy(n.content, p), nil
|
||||
}
|
||||
|
||||
// ContentMatches returns true if v matches fake file's content.
|
||||
@@ -494,7 +422,7 @@ func (n *fsNode) Walk(path string, walkFn filepath.WalkFunc) error {
|
||||
return err
|
||||
}
|
||||
if result == nil {
|
||||
return notExistError(path)
|
||||
return fmt.Errorf("cannot find '%s' to walk it", path)
|
||||
}
|
||||
return result.WalkMe(walkFn)
|
||||
}
|
||||
@@ -587,7 +515,7 @@ func isLegalFileNameForCreation(n string) bool {
|
||||
func (n *fsNode) RegExpGlob(pattern string) ([]string, error) {
|
||||
var result []string
|
||||
var expression = regexp.MustCompile(pattern)
|
||||
err := n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
||||
n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -598,9 +526,6 @@ func (n *fsNode) RegExpGlob(pattern string) ([]string, error) {
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result, nil
|
||||
}
|
||||
@@ -612,8 +537,7 @@ func (n *fsNode) RegExpGlob(pattern string) ([]string, error) {
|
||||
// This is how /bin/ls behaves.
|
||||
func (n *fsNode) Glob(pattern string) ([]string, error) {
|
||||
var result []string
|
||||
var allFiles []string
|
||||
err := n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
||||
n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -623,26 +547,11 @@ func (n *fsNode) Glob(pattern string) ([]string, error) {
|
||||
return err
|
||||
}
|
||||
if match {
|
||||
allFiles = append(allFiles, path)
|
||||
result = append(result, path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if IsHiddenFilePath(pattern) {
|
||||
result = allFiles
|
||||
} else {
|
||||
result = RemoveHiddenFiles(allFiles)
|
||||
}
|
||||
sort.Strings(result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// notExistError indicates that a file or directory does not exist.
|
||||
// Unwrapping returns os.ErrNotExist so errors.Is(err, os.ErrNotExist) works correctly.
|
||||
type notExistError string
|
||||
|
||||
func (err notExistError) Error() string { return fmt.Sprintf("'%s' doesn't exist", string(err)) }
|
||||
func (err notExistError) Unwrap() error { return os.ErrNotExist }
|
||||
@@ -1,16 +1,10 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package filesys
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
@@ -97,6 +91,7 @@ func TestMakeFsInMemory(t *testing.T) {
|
||||
func runBasicOperations(
|
||||
t *testing.T, tName string, isFSysRooted bool,
|
||||
cases []pathCase, fSys FileSystem) {
|
||||
buff := make([]byte, 500)
|
||||
for _, c := range cases {
|
||||
err := fSys.WriteFile(c.arg, []byte(content))
|
||||
if c.errStr != "" {
|
||||
@@ -133,44 +128,26 @@ func runBasicOperations(
|
||||
if fi.Name() != c.name {
|
||||
t.Fatalf("%s; expected name '%s', got '%s'", c.what, c.name, fi.Name())
|
||||
}
|
||||
buff, err := ioutil.ReadAll(f)
|
||||
count, err := f.Read(buff)
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
if string(buff) != content {
|
||||
if string(buff[:count]) != content {
|
||||
t.Fatalf("%s; unexpected buff '%s'", c.what, buff)
|
||||
}
|
||||
count, err := f.Write([]byte(shortContent))
|
||||
count, err = f.Write([]byte(shortContent))
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
if count != len(shortContent) {
|
||||
t.Fatalf("%s; unexpected count: %d", c.what, len(shortContent))
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
stuff, err = fSys.ReadFile(c.path)
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
both := content + shortContent
|
||||
if string(stuff) != both {
|
||||
t.Fatalf("%s; unexpected content '%s', expected '%s'", c.what, stuff, both)
|
||||
}
|
||||
|
||||
content := []byte(shortContent)
|
||||
if err := fSys.WriteFile(c.path, content); err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
// This ensures that modifying the original slice does not change the contents of the file.
|
||||
content[0] = '@'
|
||||
stuff, err = fSys.ReadFile(c.path)
|
||||
if err != nil {
|
||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
||||
}
|
||||
if string(stuff) != shortContent {
|
||||
t.Fatalf("%s; unexpected content '%s', expected '%s'", c.what, stuff, shortContent)
|
||||
t.Fatalf("%s; unexpected content '%s'", c.what, stuff)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,13 +441,6 @@ var bunchOfFiles = []struct {
|
||||
{
|
||||
path: filepath.Join("b", "d", "a", "c", "u"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", ".hidden_file"),
|
||||
},
|
||||
{
|
||||
path: filepath.Join("b", "d", ".hidden_dir"),
|
||||
addAsDir: true,
|
||||
},
|
||||
}
|
||||
|
||||
func makeLoadedFileTree(t *testing.T) *fsNode {
|
||||
@@ -587,7 +557,6 @@ func TestExists(t *testing.T) {
|
||||
func TestRegExpGlob(t *testing.T) {
|
||||
n := makeLoadedFileTree(t)
|
||||
expected := []string{
|
||||
filepath.Join("b", "d", ".hidden_file"),
|
||||
filepath.Join("b", "d", "a", "c", "i", "beans"),
|
||||
filepath.Join("b", "d", "a", "c", "m"),
|
||||
filepath.Join("b", "d", "a", "c", "u"),
|
||||
@@ -607,40 +576,19 @@ func TestRegExpGlob(t *testing.T) {
|
||||
|
||||
func TestGlob(t *testing.T) {
|
||||
n := makeLoadedFileTree(t)
|
||||
|
||||
tests := map[string]struct {
|
||||
globPattern string
|
||||
expectedFiles []string
|
||||
}{
|
||||
"VisibleFiles": {
|
||||
globPattern: "b/d/*",
|
||||
expectedFiles: []string{
|
||||
filepath.Join("b", "d", "x"),
|
||||
filepath.Join("b", "d", "y"),
|
||||
filepath.Join("b", "d", "z"),
|
||||
},
|
||||
},
|
||||
"HiddenFiles": {
|
||||
globPattern: "b/d/.*",
|
||||
expectedFiles: []string{
|
||||
filepath.Join("b", "d", ".hidden_file"),
|
||||
},
|
||||
},
|
||||
expected := []string{
|
||||
filepath.Join("b", "d", "x"),
|
||||
filepath.Join("b", "d", "y"),
|
||||
filepath.Join("b", "d", "z"),
|
||||
}
|
||||
|
||||
for test, c := range tests {
|
||||
t.Run(test, func(t *testing.T) {
|
||||
paths, err := n.Glob(c.globPattern)
|
||||
if err != nil {
|
||||
t.Fatalf("glob error: %v", err)
|
||||
}
|
||||
assertEqualStringSlices(t, c.expectedFiles, paths, "glob test")
|
||||
})
|
||||
paths, err := n.Glob("b/d/*")
|
||||
if err != nil {
|
||||
t.Fatalf("glob error: %v", err)
|
||||
}
|
||||
assertEqualStringSlices(t, expected, paths, "glob test")
|
||||
}
|
||||
|
||||
func assertEqualStringSlices(t *testing.T, expected, actual []string, message string) {
|
||||
t.Helper()
|
||||
if len(expected) != len(actual) {
|
||||
t.Fatalf(
|
||||
"%s; unequal sizes; len(expected)=%d, len(actual)=%d\n%+v\n%+v\n",
|
||||
@@ -838,52 +786,3 @@ func TestCleanedAbs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileOps(t *testing.T) {
|
||||
const path = "foo.txt"
|
||||
content := strings.Repeat("longest content", 100)
|
||||
|
||||
fs := MakeFsInMemory()
|
||||
f, err := fs.Create(path)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if _, err := fs.Open(path); err == nil {
|
||||
t.Fatalf("expected already opened error, got nil")
|
||||
}
|
||||
if _, err := fmt.Fprint(f, content); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := f.Close(); err == nil {
|
||||
t.Fatalf("expected already closed error, got nil")
|
||||
}
|
||||
|
||||
f, err = fs.Open(path)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
for {
|
||||
buf := make([]byte, rand.Intn(10))
|
||||
n, err := f.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if content[:n] != string(buf[:n]) {
|
||||
t.Fatalf("unexpected read: expected %q got %q", content[:n], buf[:n])
|
||||
}
|
||||
content = content[n:]
|
||||
if err != io.EOF {
|
||||
continue
|
||||
}
|
||||
if len(content) == 0 {
|
||||
break
|
||||
}
|
||||
t.Fatalf("unexpected EOF: remaining %d bytes", len(content))
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func (x fsOnDisk) CleanedAbs(
|
||||
deLinked, err := filepath.EvalSymlinks(absRoot)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf(
|
||||
"evalsymlink failure on '%s' : %w", path, err)
|
||||
"evalsymlink failure on '%s' : %v", path, err)
|
||||
}
|
||||
if x.IsDir(deLinked) {
|
||||
return ConfirmedDir(deLinked), "", nil
|
||||
@@ -88,17 +88,7 @@ func (fsOnDisk) Exists(name string) bool {
|
||||
|
||||
// Glob returns the list of matching files
|
||||
func (fsOnDisk) Glob(pattern string) ([]string, error) {
|
||||
var result []string
|
||||
allFilePaths, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if IsHiddenFilePath(pattern) {
|
||||
result = allFilePaths
|
||||
} else {
|
||||
result = RemoveHiddenFiles(allFilePaths)
|
||||
}
|
||||
return result, nil
|
||||
return filepath.Glob(pattern)
|
||||
}
|
||||
|
||||
// IsDir delegates to os.Stat and FileInfo.IsDir
|
||||
@@ -110,19 +100,6 @@ func (fsOnDisk) IsDir(name string) bool {
|
||||
return info.IsDir()
|
||||
}
|
||||
|
||||
// ReadDir delegates to os.ReadDir
|
||||
func (fsOnDisk) ReadDir(name string) ([]string, error) {
|
||||
dirEntries, err := os.ReadDir(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]string, len(dirEntries))
|
||||
for i := range dirEntries {
|
||||
result[i] = dirEntries[i].Name()
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ReadFile delegates to ioutil.ReadFile.
|
||||
func (fsOnDisk) ReadFile(name string) ([]byte, error) { return ioutil.ReadFile(name) }
|
||||
|
||||
165
api/filesys/fsondisk_test.go
Normal file
165
api/filesys/fsondisk_test.go
Normal file
@@ -0,0 +1,165 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filesys_test
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/filesys"
|
||||
)
|
||||
|
||||
func makeTestDir(t *testing.T) (FileSystem, string) {
|
||||
fSys := MakeFsOnDisk()
|
||||
td, err := ioutil.TempDir("", "kustomize_testing_dir")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
testDir, err := filepath.EvalSymlinks(td)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
if !fSys.Exists(testDir) {
|
||||
t.Fatalf("expected existence")
|
||||
}
|
||||
if !fSys.IsDir(testDir) {
|
||||
t.Fatalf("expected directory")
|
||||
}
|
||||
return fSys, testDir
|
||||
}
|
||||
|
||||
func TestCleanedAbs_1(t *testing.T) {
|
||||
fSys, testDir := makeTestDir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
d, f, err := fSys.CleanedAbs("")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
if d.String() != wd {
|
||||
t.Fatalf("unexpected d=%s", d)
|
||||
}
|
||||
if f != "" {
|
||||
t.Fatalf("unexpected f=%s", f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanedAbs_2(t *testing.T) {
|
||||
fSys, testDir := makeTestDir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
d, f, err := fSys.CleanedAbs("/")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
if d != "/" {
|
||||
t.Fatalf("unexpected d=%s", d)
|
||||
}
|
||||
if f != "" {
|
||||
t.Fatalf("unexpected f=%s", f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanedAbs_3(t *testing.T) {
|
||||
fSys, testDir := makeTestDir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
err := fSys.WriteFile(
|
||||
filepath.Join(testDir, "foo"), []byte(`foo`))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
|
||||
d, f, err := fSys.CleanedAbs(filepath.Join(testDir, "foo"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
if d.String() != testDir {
|
||||
t.Fatalf("unexpected d=%s", d)
|
||||
}
|
||||
if f != "foo" {
|
||||
t.Fatalf("unexpected f=%s", f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanedAbs_4(t *testing.T) {
|
||||
fSys, testDir := makeTestDir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
err := fSys.MkdirAll(filepath.Join(testDir, "d1", "d2"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
err = fSys.WriteFile(
|
||||
filepath.Join(testDir, "d1", "d2", "bar"),
|
||||
[]byte(`bar`))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
|
||||
d, f, err := fSys.CleanedAbs(
|
||||
filepath.Join(testDir, "d1", "d2"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
if d.String() != filepath.Join(testDir, "d1", "d2") {
|
||||
t.Fatalf("unexpected d=%s", d)
|
||||
}
|
||||
if f != "" {
|
||||
t.Fatalf("unexpected f=%s", f)
|
||||
}
|
||||
|
||||
d, f, err = fSys.CleanedAbs(
|
||||
filepath.Join(testDir, "d1", "d2", "bar"))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err=%v", err)
|
||||
}
|
||||
if d.String() != filepath.Join(testDir, "d1", "d2") {
|
||||
t.Fatalf("unexpected d=%s", d)
|
||||
}
|
||||
if f != "bar" {
|
||||
t.Fatalf("unexpected f=%s", f)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadFilesRealFS(t *testing.T) {
|
||||
fSys, testDir := makeTestDir(t)
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
err := fSys.WriteFile(path.Join(testDir, "foo"), []byte(`foo`))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
if !fSys.Exists(path.Join(testDir, "foo")) {
|
||||
t.Fatalf("expected foo")
|
||||
}
|
||||
if fSys.IsDir(path.Join(testDir, "foo")) {
|
||||
t.Fatalf("expected foo not to be a directory")
|
||||
}
|
||||
|
||||
err = fSys.WriteFile(path.Join(testDir, "bar"), []byte(`bar`))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %s", err)
|
||||
}
|
||||
|
||||
files, err := fSys.Glob(path.Join("testDir", "*"))
|
||||
expected := []string{
|
||||
path.Join(testDir, "bar"),
|
||||
path.Join(testDir, "foo"),
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("expected no error")
|
||||
}
|
||||
if reflect.DeepEqual(files, expected) {
|
||||
t.Fatalf("incorrect files found by glob: %v", files)
|
||||
}
|
||||
}
|
||||
30
api/filesys/util.go
Normal file
30
api/filesys/util.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package filesys
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
// RootedPath returns a rooted path, e.g. "/foo/bar" as
|
||||
// opposed to "foo/bar".
|
||||
func RootedPath(elem ...string) string {
|
||||
return Separator + filepath.Join(elem...)
|
||||
}
|
||||
|
||||
// StripTrailingSeps trims trailing filepath separators from input.
|
||||
func StripTrailingSeps(s string) string {
|
||||
k := len(s)
|
||||
for k > 0 && s[k-1] == filepath.Separator {
|
||||
k--
|
||||
}
|
||||
return s[:k]
|
||||
}
|
||||
|
||||
// StripLeadingSeps trims leading filepath separators from input.
|
||||
func StripLeadingSeps(s string) string {
|
||||
k := 0
|
||||
for k < len(s) && s[k] == filepath.Separator {
|
||||
k++
|
||||
}
|
||||
return s[k:]
|
||||
}
|
||||
205
api/filesys/util_test.go
Normal file
205
api/filesys/util_test.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package filesys_test
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/filesys"
|
||||
)
|
||||
|
||||
// Confirm behavior of filepath.Match
|
||||
func TestFilePathMatch(t *testing.T) {
|
||||
cases := []struct {
|
||||
pattern string
|
||||
path string
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
pattern: "*e*",
|
||||
path: "hey",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "*e*",
|
||||
path: "hay",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
pattern: "*e*",
|
||||
path: filepath.Join("h", "e", "y"),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
pattern: "*/e/*",
|
||||
path: filepath.Join("h", "e", "y"),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "h/e/*",
|
||||
path: filepath.Join("h", "e", "y"),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "*/e/y",
|
||||
path: filepath.Join("h", "e", "y"),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "*/*/*",
|
||||
path: filepath.Join("h", "e", "y"),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
pattern: "*/*/*",
|
||||
path: filepath.Join("h", "e", "y", "there"),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
pattern: "*/*/*/t*e",
|
||||
path: filepath.Join("h", "e", "y", "there"),
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
for _, item := range cases {
|
||||
match, err := filepath.Match(item.pattern, item.path)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if match != item.expected {
|
||||
t.Fatalf("'%s' '%s' %v\n", item.pattern, item.path, match)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm behavior of filepath.Split
|
||||
func TestFilePathSplit(t *testing.T) {
|
||||
cases := []struct {
|
||||
full string
|
||||
dir string
|
||||
file string
|
||||
}{
|
||||
{
|
||||
full: "",
|
||||
dir: "",
|
||||
file: "",
|
||||
},
|
||||
{
|
||||
full: SelfDir,
|
||||
dir: "",
|
||||
file: SelfDir,
|
||||
},
|
||||
{
|
||||
full: "rabbit.jpg",
|
||||
dir: "",
|
||||
file: "rabbit.jpg",
|
||||
},
|
||||
{
|
||||
full: "/beans",
|
||||
dir: "/",
|
||||
file: "beans",
|
||||
},
|
||||
{
|
||||
full: "/home/foo/bar",
|
||||
dir: "/home/foo/",
|
||||
file: "bar",
|
||||
},
|
||||
{
|
||||
full: "/usr/local/",
|
||||
dir: "/usr/local/",
|
||||
file: "",
|
||||
},
|
||||
{
|
||||
full: "/usr//local//go",
|
||||
dir: "/usr//local//",
|
||||
file: "go",
|
||||
},
|
||||
}
|
||||
for _, p := range cases {
|
||||
dir, file := filepath.Split(p.full)
|
||||
if dir != p.dir || file != p.file {
|
||||
t.Fatalf(
|
||||
"in '%s',\ngot dir='%s' (expected '%s'),\n got file='%s' (expected %s).",
|
||||
p.full, dir, p.dir, file, p.file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripTrailingSeps(t *testing.T) {
|
||||
cases := []struct {
|
||||
full string
|
||||
rem string
|
||||
}{
|
||||
{
|
||||
full: "foo",
|
||||
rem: "foo",
|
||||
},
|
||||
{
|
||||
full: "",
|
||||
rem: "",
|
||||
},
|
||||
{
|
||||
full: "foo/",
|
||||
rem: "foo",
|
||||
},
|
||||
{
|
||||
full: "foo///bar///",
|
||||
rem: "foo///bar",
|
||||
},
|
||||
{
|
||||
full: "/////",
|
||||
rem: "",
|
||||
},
|
||||
{
|
||||
full: "/",
|
||||
rem: "",
|
||||
},
|
||||
}
|
||||
for _, p := range cases {
|
||||
dir := StripTrailingSeps(p.full)
|
||||
if dir != p.rem {
|
||||
t.Fatalf(
|
||||
"in '%s', got dir='%s' (expected '%s')",
|
||||
p.full, dir, p.rem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStripLeadingSeps(t *testing.T) {
|
||||
cases := []struct {
|
||||
full string
|
||||
rem string
|
||||
}{
|
||||
{
|
||||
full: "foo",
|
||||
rem: "foo",
|
||||
},
|
||||
{
|
||||
full: "",
|
||||
rem: "",
|
||||
},
|
||||
{
|
||||
full: "/foo",
|
||||
rem: "foo",
|
||||
},
|
||||
{
|
||||
full: "///foo///bar///",
|
||||
rem: "foo///bar///",
|
||||
},
|
||||
{
|
||||
full: "/////",
|
||||
rem: "",
|
||||
},
|
||||
{
|
||||
full: "/",
|
||||
rem: "",
|
||||
},
|
||||
}
|
||||
for _, p := range cases {
|
||||
dir := StripLeadingSeps(p.full)
|
||||
if dir != p.rem {
|
||||
t.Fatalf(
|
||||
"in '%s', got dir='%s' (expected '%s')",
|
||||
p.full, dir, p.rem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
type annoMap map[string]string
|
||||
|
||||
type Filter struct {
|
||||
// Annotations is the set of annotations to apply to the inputs
|
||||
Annotations annoMap `yaml:"annotations,omitempty"`
|
||||
|
||||
// FsSlice contains the FieldSpecs to locate the namespace field
|
||||
FsSlice types.FsSlice
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
keys := yaml.SortedMapKeys(f.Annotations)
|
||||
_, err := kio.FilterAll(yaml.FilterFunc(
|
||||
func(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
for _, k := range keys {
|
||||
if err := node.PipeE(fsslice.Filter{
|
||||
FsSlice: f.FsSlice,
|
||||
SetValue: filtersutil.SetEntry(
|
||||
k, f.Annotations[k], yaml.NodeTagString),
|
||||
CreateKind: yaml.MappingNode, // Annotations are MappingNodes.
|
||||
CreateTag: yaml.NodeTagMap,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return node, nil
|
||||
})).Filter(nodes)
|
||||
return nodes, err
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
var annosFs = builtinconfig.MakeDefaultConfig().CommonAnnotations
|
||||
|
||||
func TestAnnotations_Filter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expectedOutput string
|
||||
filter Filter
|
||||
fsslice types.FsSlice
|
||||
}{
|
||||
"add": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
auto: ford
|
||||
bean: cannellini
|
||||
clown: emmett kelley
|
||||
dragon: smaug
|
||||
`,
|
||||
filter: Filter{Annotations: annoMap{
|
||||
"clown": "emmett kelley",
|
||||
"auto": "ford",
|
||||
"dragon": "smaug",
|
||||
"bean": "cannellini",
|
||||
}},
|
||||
},
|
||||
"update": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: superman
|
||||
fiend: luthor
|
||||
bean: cannellini
|
||||
clown: emmett kelley
|
||||
`,
|
||||
filter: Filter{Annotations: annoMap{
|
||||
"clown": "emmett kelley",
|
||||
"hero": "superman",
|
||||
"fiend": "luthor",
|
||||
"bean": "cannellini",
|
||||
}},
|
||||
},
|
||||
"data-fieldspecs": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
sleater: kinney
|
||||
a:
|
||||
b:
|
||||
sleater: kinney
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
sleater: kinney
|
||||
a:
|
||||
b:
|
||||
sleater: kinney
|
||||
`,
|
||||
filter: Filter{Annotations: annoMap{
|
||||
"sleater": "kinney",
|
||||
}},
|
||||
fsslice: []types.FieldSpec{
|
||||
{
|
||||
Path: "a/b",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"number": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
2: ford
|
||||
clown: "1"
|
||||
`,
|
||||
filter: Filter{Annotations: annoMap{
|
||||
"clown": "1",
|
||||
"2": "ford",
|
||||
}},
|
||||
},
|
||||
|
||||
// test quoting of values which are not considered strings in yaml 1.1
|
||||
"yaml_1_1_compatibility": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
a: "y"
|
||||
b: y1
|
||||
c: "yes"
|
||||
d: yes1
|
||||
e: "true"
|
||||
f: true1
|
||||
`,
|
||||
filter: Filter{Annotations: annoMap{
|
||||
"a": "y",
|
||||
"b": "y1",
|
||||
"c": "yes",
|
||||
"d": "yes1",
|
||||
"e": "true",
|
||||
"f": "true1",
|
||||
}},
|
||||
},
|
||||
|
||||
// test quoting of values which are not considered strings in yaml 1.1
|
||||
"null_annotations": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations: null
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
annotations:
|
||||
a: a1
|
||||
b: b1
|
||||
`,
|
||||
filter: Filter{Annotations: annoMap{
|
||||
"a": "a1",
|
||||
"b": "b1",
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
filter := tc.filter
|
||||
filter.FsSlice = append(annosFs, tc.fsslice...)
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expectedOutput),
|
||||
strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package annotations contains a kio.Filter implementation of the kustomize
|
||||
// annotations transformer.
|
||||
package annotations
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package annotations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
fss := builtinconfig.MakeDefaultConfig().CommonAnnotations
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`)}},
|
||||
Filters: []kio.Filter{Filter{
|
||||
Annotations: map[string]string{
|
||||
"foo": "bar",
|
||||
"booleanValue": "true",
|
||||
"numberValue": "42",
|
||||
},
|
||||
FsSlice: fss,
|
||||
}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo
|
||||
// metadata:
|
||||
// name: instance
|
||||
// annotations:
|
||||
// booleanValue: "true"
|
||||
// foo: bar
|
||||
// numberValue: "42"
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// annotations:
|
||||
// booleanValue: "true"
|
||||
// foo: bar
|
||||
// numberValue: "42"
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package filters
|
||||
|
||||
// Package filters collects various implementations
|
||||
// sigs.k8s.io/kustomize/kyaml/kio.Filter used by kustomize
|
||||
// transformers to modify kubernetes objects.
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package fieldspec contains a yaml.Filter to modify a resource
|
||||
// that matches the FieldSpec.
|
||||
package fieldspec
|
||||
@@ -1,61 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fieldspec_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
. "sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
in := &kio.ByteReader{
|
||||
Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`),
|
||||
}
|
||||
fltr := Filter{
|
||||
CreateKind: yaml.ScalarNode,
|
||||
SetValue: filtersutil.SetScalar("green"),
|
||||
FieldSpec: types.FieldSpec{Path: "a/b", CreateIfNotPresent: true},
|
||||
}
|
||||
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{in},
|
||||
Filters: []kio.Filter{kio.FilterAll(fltr)},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo
|
||||
// metadata:
|
||||
// name: instance
|
||||
// a:
|
||||
// b: green
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// a:
|
||||
// b: green
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fieldspec
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/internal/utils"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
var _ yaml.Filter = Filter{}
|
||||
|
||||
// Filter possibly mutates its object argument using a FieldSpec.
|
||||
// If the object matches the FieldSpec, and the node found
|
||||
// by following the fieldSpec's path is non-null, this filter calls
|
||||
// the setValue function on the node at the end of the path.
|
||||
// If any part of the path doesn't exist, the filter returns
|
||||
// without doing anything and without error, unless it was set
|
||||
// to create the path. If set to create, it creates a tree of maps
|
||||
// along the path, and the leaf node gets the setValue called on it.
|
||||
// Error on GVK mismatch, empty or poorly formed path.
|
||||
// Filter expect kustomize style paths, not JSON paths.
|
||||
// Filter stores internal state and should not be reused
|
||||
type Filter struct {
|
||||
// FieldSpec contains the path to the value to set.
|
||||
FieldSpec types.FieldSpec `yaml:"fieldSpec"`
|
||||
|
||||
// Set the field using this function
|
||||
SetValue filtersutil.SetFn
|
||||
|
||||
// CreateKind defines the type of node to create if the field is not found
|
||||
CreateKind yaml.Kind
|
||||
|
||||
CreateTag string
|
||||
|
||||
// path keeps internal state about the current path
|
||||
path []string
|
||||
}
|
||||
|
||||
func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
||||
// check if the FieldSpec applies to the object
|
||||
if match := isMatchGVK(fltr.FieldSpec, obj); !match {
|
||||
return obj, nil
|
||||
}
|
||||
fltr.path = utils.PathSplitter(fltr.FieldSpec.Path, "/")
|
||||
if err := fltr.filter(obj); err != nil {
|
||||
s, _ := obj.String()
|
||||
return nil, errors.WrapPrefixf(err,
|
||||
"considering field '%s' of object\n%v", fltr.FieldSpec.Path, s)
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// Recursively called.
|
||||
func (fltr Filter) filter(obj *yaml.RNode) error {
|
||||
if len(fltr.path) == 0 {
|
||||
// found the field -- set its value
|
||||
return fltr.SetValue(obj)
|
||||
}
|
||||
if obj.IsTaggedNull() || obj.IsNil() {
|
||||
return nil
|
||||
}
|
||||
switch obj.YNode().Kind {
|
||||
case yaml.SequenceNode:
|
||||
return fltr.handleSequence(obj)
|
||||
case yaml.MappingNode:
|
||||
return fltr.handleMap(obj)
|
||||
case yaml.AliasNode:
|
||||
return fltr.filter(yaml.NewRNode(obj.YNode().Alias))
|
||||
default:
|
||||
return errors.Errorf("expected sequence or mapping node")
|
||||
}
|
||||
}
|
||||
|
||||
// handleMap calls filter on the map field matching the next path element
|
||||
func (fltr Filter) handleMap(obj *yaml.RNode) error {
|
||||
fieldName, isSeq := isSequenceField(fltr.path[0])
|
||||
if fieldName == "" {
|
||||
return fmt.Errorf("cannot set or create an empty field name")
|
||||
}
|
||||
// lookup the field matching the next path element
|
||||
var operation yaml.Filter
|
||||
var kind yaml.Kind
|
||||
tag := yaml.NodeTagEmpty
|
||||
switch {
|
||||
case !fltr.FieldSpec.CreateIfNotPresent || fltr.CreateKind == 0 || isSeq:
|
||||
// don't create the field if we don't find it
|
||||
operation = yaml.Lookup(fieldName)
|
||||
if isSeq {
|
||||
// The query path thinks this field should be a sequence;
|
||||
// accept this hint for use later if the tag is NodeTagNull.
|
||||
kind = yaml.SequenceNode
|
||||
}
|
||||
case len(fltr.path) <= 1:
|
||||
// create the field if it is missing: use the provided node kind
|
||||
operation = yaml.LookupCreate(fltr.CreateKind, fieldName)
|
||||
kind = fltr.CreateKind
|
||||
tag = fltr.CreateTag
|
||||
default:
|
||||
// create the field if it is missing: must be a mapping node
|
||||
operation = yaml.LookupCreate(yaml.MappingNode, fieldName)
|
||||
kind = yaml.MappingNode
|
||||
tag = yaml.NodeTagMap
|
||||
}
|
||||
|
||||
// locate (or maybe create) the field
|
||||
field, err := obj.Pipe(operation)
|
||||
if err != nil {
|
||||
return errors.WrapPrefixf(err, "fieldName: %s", fieldName)
|
||||
}
|
||||
if field == nil {
|
||||
// No error if field not found.
|
||||
return nil
|
||||
}
|
||||
|
||||
// if the value exists, but is null and kind is set,
|
||||
// then change it to the creation type
|
||||
// TODO: update yaml.LookupCreate to support this
|
||||
if field.YNode().Tag == yaml.NodeTagNull && yaml.IsCreate(kind) {
|
||||
field.YNode().Kind = kind
|
||||
field.YNode().Tag = tag
|
||||
}
|
||||
|
||||
// copy the current fltr and change the path on the copy
|
||||
var next = fltr
|
||||
// call filter for the next path element on the matching field
|
||||
next.path = fltr.path[1:]
|
||||
return next.filter(field)
|
||||
}
|
||||
|
||||
// seq calls filter on all sequence elements
|
||||
func (fltr Filter) handleSequence(obj *yaml.RNode) error {
|
||||
if err := obj.VisitElements(func(node *yaml.RNode) error {
|
||||
// recurse on each element -- re-allocating a Filter is
|
||||
// not strictly required, but is more consistent with field
|
||||
// and less likely to have side effects
|
||||
// keep the entire path -- it does not contain parts for sequences
|
||||
return fltr.filter(node)
|
||||
}); err != nil {
|
||||
return errors.WrapPrefixf(err,
|
||||
"visit traversal on path: %v", fltr.path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isSequenceField returns true if the path element is for a sequence field.
|
||||
// isSequence also returns the path element with the '[]' suffix trimmed
|
||||
func isSequenceField(name string) (string, bool) {
|
||||
shorter := strings.TrimSuffix(name, "[]")
|
||||
return shorter, shorter != name
|
||||
}
|
||||
|
||||
// isMatchGVK returns true if the fs.GVK matches the obj GVK.
|
||||
func isMatchGVK(fs types.FieldSpec, obj *yaml.RNode) bool {
|
||||
if kind := obj.GetKind(); fs.Kind != "" && fs.Kind != kind {
|
||||
// kind doesn't match
|
||||
return false
|
||||
}
|
||||
|
||||
// parse the group and version from the apiVersion field
|
||||
group, version := resid.ParseGroupVersion(obj.GetApiVersion())
|
||||
|
||||
if fs.Group != "" && fs.Group != group {
|
||||
// group doesn't match
|
||||
return false
|
||||
}
|
||||
|
||||
if fs.Version != "" && fs.Version != version {
|
||||
// version doesn't match
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,568 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fieldspec_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestFilter_Filter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expected string
|
||||
filter fieldspec.Filter
|
||||
fieldSpec string
|
||||
error string
|
||||
}{
|
||||
"path not found": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo
|
||||
kind: Bar
|
||||
xxx:
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo
|
||||
kind: Bar
|
||||
xxx:
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
"empty path": {
|
||||
fieldSpec: `
|
||||
group: foo
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
xxx:
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo
|
||||
kind: Bar
|
||||
xxx:
|
||||
`,
|
||||
error: `considering field '' of object
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
xxx:
|
||||
: cannot set or create an empty field name`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"update": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: e
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"update-kind-not-match": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
kind: Bar1
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar2
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar2
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"update-group-not-match": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo2/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo2/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"update-version-not-match": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
version: v1beta1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta2
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta2
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"bad-version": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
version: v1beta1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta2/something
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta2/something
|
||||
kind: Bar
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"bad-meta": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: foo
|
||||
version: v1beta1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
expected: `
|
||||
a:
|
||||
b: c
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"miss-match-type": {
|
||||
fieldSpec: `
|
||||
path: a/b/c
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
kind: Bar
|
||||
a:
|
||||
b: a
|
||||
`,
|
||||
error: `considering field 'a/b/c' of object
|
||||
kind: Bar
|
||||
a:
|
||||
b: a
|
||||
: expected sequence or mapping node`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
"add": {
|
||||
fieldSpec: `
|
||||
path: a/b/c/d
|
||||
group: foo
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {}
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {b: {c: {d: e}}}
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
"update-in-sequence": {
|
||||
fieldSpec: `
|
||||
path: a/b[]/c/d
|
||||
group: foo
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c:
|
||||
d: a
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c:
|
||||
d: e
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
},
|
||||
},
|
||||
|
||||
// Don't create a sequence
|
||||
"empty-sequence-no-create": {
|
||||
fieldSpec: `
|
||||
path: a/b[]/c/d
|
||||
group: foo
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {}
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a: {}
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
// Create a new field for an element in a sequence
|
||||
"empty-sequence-create": {
|
||||
fieldSpec: `
|
||||
path: a/b[]/c/d
|
||||
group: foo
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c: {}
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1beta1
|
||||
kind: Bar
|
||||
a:
|
||||
b:
|
||||
- c: {d: e}
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
"group v1": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
group: v1
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
"version v1": {
|
||||
fieldSpec: `
|
||||
path: a/b
|
||||
version: v1
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b: e
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
"successfully set field on array entry no sequence hint": {
|
||||
fieldSpec: `
|
||||
path: spec/containers/image
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
- image: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
- image: bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
"successfully set field on array entry with sequence hint": {
|
||||
fieldSpec: `
|
||||
path: spec/containers[]/image
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
- image: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
- image: bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
"failure to set field on array entry with sequence hint in path": {
|
||||
fieldSpec: `
|
||||
path: spec/containers[]/image
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers: []
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
|
||||
"failure to set field on array entry, no sequence hint in path": {
|
||||
fieldSpec: `
|
||||
path: spec/containers/image
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
spec:
|
||||
containers:
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
"fieldname with slash '/'": {
|
||||
fieldSpec: `
|
||||
path: a/b\/c/d
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b/c:
|
||||
d: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b/c:
|
||||
d: bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
"fieldname with multiple '/'": {
|
||||
fieldSpec: `
|
||||
path: a/b\/c/d\/e/f
|
||||
version: v1
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b/c:
|
||||
d/e:
|
||||
f: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: Bar
|
||||
a:
|
||||
b/c:
|
||||
d/e:
|
||||
f: bar
|
||||
`,
|
||||
filter: fieldspec.Filter{
|
||||
SetValue: filtersutil.SetScalar("bar"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for n := range testCases {
|
||||
tc := testCases[n]
|
||||
t.Run(n, func(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte(tc.fieldSpec), &tc.filter.FieldSpec)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
rw := &kio.ByteReadWriter{
|
||||
Reader: bytes.NewBufferString(tc.input),
|
||||
Writer: out,
|
||||
OmitReaderAnnotations: true,
|
||||
}
|
||||
|
||||
// run the filter
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{rw},
|
||||
Filters: []kio.Filter{kio.FilterAll(tc.filter)},
|
||||
Outputs: []kio.Writer{rw},
|
||||
}.Execute()
|
||||
if tc.error != "" {
|
||||
if !assert.EqualError(t, err, tc.error) {
|
||||
t.FailNow()
|
||||
}
|
||||
// stop rest of test
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// check results
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expected),
|
||||
strings.TrimSpace(out.String())) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package filtersutil
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// SetFn is a function that accepts an RNode to possibly modify.
|
||||
type SetFn func(*yaml.RNode) error
|
||||
|
||||
// SetScalar returns a SetFn to set a scalar value
|
||||
func SetScalar(value string) SetFn {
|
||||
return func(node *yaml.RNode) error {
|
||||
return node.PipeE(yaml.FieldSetter{StringValue: value})
|
||||
}
|
||||
}
|
||||
|
||||
// SetEntry returns a SetFn to set an entry in a map
|
||||
func SetEntry(key, value, tag string) SetFn {
|
||||
n := &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: value,
|
||||
Tag: tag,
|
||||
}
|
||||
if tag == yaml.NodeTagString && yaml.IsYaml1_1NonString(n) {
|
||||
n.Style = yaml.DoubleQuotedStyle
|
||||
}
|
||||
return func(node *yaml.RNode) error {
|
||||
return node.PipeE(yaml.FieldSetter{
|
||||
Name: key,
|
||||
Value: yaml.NewRNode(n),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package fsslice contains a yaml.Filter to modify a resource if
|
||||
// it matches one or more FieldSpec entries.
|
||||
package fsslice
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fsslice_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
in := &kio.ByteReader{
|
||||
Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`),
|
||||
}
|
||||
fltr := fsslice.Filter{
|
||||
CreateKind: yaml.ScalarNode,
|
||||
SetValue: filtersutil.SetScalar("green"),
|
||||
FsSlice: []types.FieldSpec{
|
||||
{Path: "a/b", CreateIfNotPresent: true},
|
||||
},
|
||||
}
|
||||
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{in},
|
||||
Filters: []kio.Filter{kio.FilterAll(fltr)},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo
|
||||
// metadata:
|
||||
// name: instance
|
||||
// a:
|
||||
// b: green
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// a:
|
||||
// b: green
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fsslice
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
var _ yaml.Filter = Filter{}
|
||||
|
||||
// Filter ranges over an FsSlice to modify fields on a single object.
|
||||
// An FsSlice is a range of FieldSpecs. A FieldSpec is a GVK plus a path.
|
||||
type Filter struct {
|
||||
// FieldSpecList list of FieldSpecs to set
|
||||
FsSlice types.FsSlice `yaml:"fsSlice"`
|
||||
|
||||
// SetValue is called on each field that matches one of the FieldSpecs
|
||||
SetValue filtersutil.SetFn
|
||||
|
||||
// CreateKind is used to create fields that do not exist
|
||||
CreateKind yaml.Kind
|
||||
|
||||
// CreateTag is used to set the tag if encountering a null field
|
||||
CreateTag string
|
||||
}
|
||||
|
||||
func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
||||
for i := range fltr.FsSlice {
|
||||
// apply this FieldSpec
|
||||
// create a new filter for each iteration because they
|
||||
// store internal state about the field paths
|
||||
_, err := (&fieldspec.Filter{
|
||||
FieldSpec: fltr.FsSlice[i],
|
||||
SetValue: fltr.SetValue,
|
||||
CreateKind: fltr.CreateKind,
|
||||
CreateTag: fltr.CreateTag,
|
||||
}).Filter(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fsslice_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
. "sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
type TestCase struct {
|
||||
input string
|
||||
expected string
|
||||
filter Filter
|
||||
fsSlice string
|
||||
error string
|
||||
}
|
||||
|
||||
var tests = map[string]TestCase{
|
||||
"empty": {
|
||||
fsSlice: `
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
`,
|
||||
filter: Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
"two": {
|
||||
fsSlice: `
|
||||
- path: a/b
|
||||
group: foo
|
||||
version: v1
|
||||
create: true
|
||||
kind: Bar
|
||||
- path: q/r[]/s/t
|
||||
group: foo
|
||||
version: v1
|
||||
create: true
|
||||
kind: Bar
|
||||
`,
|
||||
input: `
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
q:
|
||||
r:
|
||||
- s: {}
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: foo/v1
|
||||
kind: Bar
|
||||
q:
|
||||
r:
|
||||
- s: {t: e}
|
||||
a:
|
||||
b: e
|
||||
`,
|
||||
filter: Filter{
|
||||
SetValue: filtersutil.SetScalar("e"),
|
||||
CreateKind: yaml.ScalarNode,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
for name := range tests {
|
||||
test := tests[name]
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := yaml.Unmarshal([]byte(test.fsSlice), &test.filter.FsSlice)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
rw := &kio.ByteReadWriter{
|
||||
Reader: bytes.NewBufferString(test.input),
|
||||
Writer: out,
|
||||
OmitReaderAnnotations: true,
|
||||
}
|
||||
|
||||
// run the filter
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{rw},
|
||||
Filters: []kio.Filter{kio.FilterAll(test.filter)},
|
||||
Outputs: []kio.Writer{rw},
|
||||
}.Execute()
|
||||
if test.error != "" {
|
||||
if !assert.EqualError(t, err, test.error) {
|
||||
t.FailNow()
|
||||
}
|
||||
// stop rest of test
|
||||
return
|
||||
}
|
||||
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// check results
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(test.expected),
|
||||
strings.TrimSpace(out.String())) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// Package gkesagenerator contains a kio.Filter that that generates a
|
||||
// iampolicy-related resources for a given cloud provider
|
||||
package iampolicygenerator
|
||||
@@ -1,46 +0,0 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package iampolicygenerator
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
f := Filter{}
|
||||
var err = yaml.Unmarshal([]byte(`
|
||||
cloud: gke
|
||||
kubernetesService:
|
||||
namespace: k8s-namespace
|
||||
name: k8s-sa-name
|
||||
serviceAccount:
|
||||
name: gsa-name
|
||||
projectId: project-id
|
||||
`), &f)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{},
|
||||
Filters: []kio.Filter{f},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: v1
|
||||
// kind: ServiceAccount
|
||||
// metadata:
|
||||
// annotations:
|
||||
// iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
|
||||
// name: k8s-sa-name
|
||||
// namespace: k8s-namespace
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package iampolicygenerator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
IAMPolicyGenerator types.IAMPolicyGeneratorArgs `json:",inline,omitempty" yaml:",inline,omitempty"`
|
||||
}
|
||||
|
||||
// Filter adds a GKE service account object to nodes
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
switch f.IAMPolicyGenerator.Cloud {
|
||||
case types.GKE:
|
||||
IAMPolicyResources, err := f.generateGkeIAMPolicyResources()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes = append(nodes, IAMPolicyResources...)
|
||||
default:
|
||||
return nil, fmt.Errorf("cloud provider %s not supported yet", f.IAMPolicyGenerator.Cloud)
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (f Filter) generateGkeIAMPolicyResources() ([]*yaml.RNode, error) {
|
||||
var result []*yaml.RNode
|
||||
input := fmt.Sprintf(`
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
annotations:
|
||||
iam.gke.io/gcp-service-account: %s@%s.iam.gserviceaccount.com
|
||||
name: %s
|
||||
`, f.IAMPolicyGenerator.ServiceAccount.Name,
|
||||
f.IAMPolicyGenerator.ProjectId,
|
||||
f.IAMPolicyGenerator.KubernetesService.Name)
|
||||
|
||||
if f.IAMPolicyGenerator.Namespace != "" {
|
||||
input = input + fmt.Sprintf("\n namespace: %s", f.IAMPolicyGenerator.Namespace)
|
||||
}
|
||||
|
||||
sa, err := yaml.Parse(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return append(result, sa), nil
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package iampolicygenerator
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
args types.IAMPolicyGeneratorArgs
|
||||
expected string
|
||||
}{
|
||||
"with namespace": {
|
||||
args: types.IAMPolicyGeneratorArgs{
|
||||
Cloud: types.GKE,
|
||||
KubernetesService: types.KubernetesService{
|
||||
Namespace: "k8s-namespace",
|
||||
Name: "k8s-sa-name",
|
||||
},
|
||||
ServiceAccount: types.ServiceAccount{
|
||||
Name: "gsa-name",
|
||||
ProjectId: "project-id",
|
||||
},
|
||||
},
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
annotations:
|
||||
iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
|
||||
name: k8s-sa-name
|
||||
namespace: k8s-namespace
|
||||
`,
|
||||
},
|
||||
"without namespace": {
|
||||
args: types.IAMPolicyGeneratorArgs{
|
||||
Cloud: types.GKE,
|
||||
KubernetesService: types.KubernetesService{
|
||||
Name: "k8s-sa-name",
|
||||
},
|
||||
ServiceAccount: types.ServiceAccount{
|
||||
Name: "gsa-name",
|
||||
ProjectId: "project-id",
|
||||
},
|
||||
},
|
||||
expected: `
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
annotations:
|
||||
iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
|
||||
name: k8s-sa-name
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
f := Filter{
|
||||
IAMPolicyGenerator: tc.args,
|
||||
}
|
||||
actual := filtertest.RunFilter(t, "", f)
|
||||
if !assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(actual)) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package imagetag contains two kio.Filter implementations to cover the
|
||||
// functionality of the kustomize imagetag transformer.
|
||||
//
|
||||
// Filter updates fields based on a FieldSpec and an ImageTag.
|
||||
//
|
||||
// LegacyFilter doesn't use a FieldSpec, and instead only updates image
|
||||
// references if the field is name image and it is underneath a field called
|
||||
// either containers or initContainers.
|
||||
package imagetag
|
||||
@@ -1,126 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package imagetag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- name: FooBar
|
||||
image: nginx
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- name: BarFoo
|
||||
image: nginx:1.2.1
|
||||
`)}},
|
||||
Filters: []kio.Filter{Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
Digest: "12345",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers[]/image",
|
||||
},
|
||||
},
|
||||
}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo
|
||||
// metadata:
|
||||
// name: instance
|
||||
// spec:
|
||||
// containers:
|
||||
// - name: FooBar
|
||||
// image: apache@12345
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// spec:
|
||||
// containers:
|
||||
// - name: BarFoo
|
||||
// image: apache@12345
|
||||
}
|
||||
|
||||
func ExampleLegacyFilter() {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- name: FooBar
|
||||
image: nginx
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- name: BarFoo
|
||||
image: nginx:1.2.1
|
||||
`)}},
|
||||
Filters: []kio.Filter{LegacyFilter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
Digest: "12345",
|
||||
},
|
||||
}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo
|
||||
// metadata:
|
||||
// name: instance
|
||||
// spec:
|
||||
// containers:
|
||||
// - name: FooBar
|
||||
// image: apache@12345
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// spec:
|
||||
// containers:
|
||||
// - name: BarFoo
|
||||
// image: apache@12345
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package imagetag
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Filter modifies an "image tag", the value used to specify the
|
||||
// name, tag, version digest etc. of (docker) container images
|
||||
// used by a pod template.
|
||||
type Filter struct {
|
||||
// imageTag is the tag we want to apply to the inputs
|
||||
// The name of the image is used as a key, and other fields
|
||||
// can specify a new name, tag, etc.
|
||||
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
|
||||
|
||||
// FsSlice contains the FieldSpecs to locate an image field,
|
||||
// e.g. Path: "spec/myContainers[]/image"
|
||||
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
_, err := kio.FilterAll(yaml.FilterFunc(f.filter)).Filter(nodes)
|
||||
return nodes, err
|
||||
}
|
||||
|
||||
func (f Filter) filter(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
// FsSlice is an allowlist, not a denyList, so to deny
|
||||
// something via configuration a new config mechanism is
|
||||
// needed. Until then, hardcode it.
|
||||
if f.isOnDenyList(node) {
|
||||
return node, nil
|
||||
}
|
||||
if err := node.PipeE(fsslice.Filter{
|
||||
FsSlice: f.FsSlice,
|
||||
SetValue: updateImageTagFn(f.ImageTag),
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func (f Filter) isOnDenyList(node *yaml.RNode) bool {
|
||||
meta, err := node.GetMeta()
|
||||
if err != nil {
|
||||
// A missing 'meta' field will cause problems elsewhere;
|
||||
// ignore it here to keep the signature simple.
|
||||
return false
|
||||
}
|
||||
// Ignore CRDs
|
||||
// https://github.com/kubernetes-sigs/kustomize/issues/890
|
||||
return meta.Kind == `CustomResourceDefinition`
|
||||
}
|
||||
|
||||
func updateImageTagFn(imageTag types.Image) filtersutil.SetFn {
|
||||
return func(node *yaml.RNode) error {
|
||||
return node.PipeE(imageTagUpdater{
|
||||
ImageTag: imageTag,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,674 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package imagetag
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func TestImageTagUpdater_Filter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expectedOutput string
|
||||
filter Filter
|
||||
fsSlice types.FsSlice
|
||||
}{
|
||||
"ignore CustomResourceDefinition": {
|
||||
input: `
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: whatever
|
||||
spec:
|
||||
containers:
|
||||
- image: whatever
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: whatever
|
||||
spec:
|
||||
containers:
|
||||
- image: whatever
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "whatever",
|
||||
NewName: "theImageShouldNotChangeInACrd",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"legacy multiple images in containers": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.2.1
|
||||
- image: nginx:2.1.2
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: apache@12345
|
||||
- image: apache@12345
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
Digest: "12345",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"legacy both containers and initContainers": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.2.1
|
||||
- image: tomcat:1.2.3
|
||||
initContainers:
|
||||
- image: nginx:1.2.1
|
||||
- image: apache:1.2.3
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: apache:3.2.1
|
||||
- image: tomcat:1.2.3
|
||||
initContainers:
|
||||
- image: apache:3.2.1
|
||||
- image: apache:1.2.3
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
NewTag: "3.2.1",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/initContainers/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"legacy updates at multiple depths": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.2.1
|
||||
- image: tomcat:1.2.3
|
||||
template:
|
||||
spec:
|
||||
initContainers:
|
||||
- image: nginx:1.2.1
|
||||
- image: apache:1.2.3
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: apache:3.2.1
|
||||
- image: tomcat:1.2.3
|
||||
template:
|
||||
spec:
|
||||
initContainers:
|
||||
- image: apache:3.2.1
|
||||
- image: apache:1.2.3
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
NewTag: "3.2.1",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"update with digest": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
image: nginx:1.2.1
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
image: apache@12345
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
Digest: "12345",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"multiple matches in sequence": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.2.1
|
||||
- image: not_nginx@54321
|
||||
- image: nginx:1.2.1
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: apache:3.2.1
|
||||
- image: not_nginx@54321
|
||||
- image: apache:3.2.1
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
NewTag: "3.2.1",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"new Tag": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:v2
|
||||
name: nginx-tagged
|
||||
- image: nginx:v2
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx:v2
|
||||
name: nginx-notag
|
||||
- image: nginx:v2
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewTag: "v2",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/spec/containers[]/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers[]/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"newImage": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: busybox:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: busybox
|
||||
name: nginx-notag
|
||||
- image: busybox@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "busybox",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/spec/containers[]/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers[]/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"newImageAndTag": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox:v3
|
||||
name: nginx-tagged
|
||||
- image: busybox:v3
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: busybox:v3
|
||||
name: nginx-notag
|
||||
- image: busybox:v3
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "busybox",
|
||||
NewTag: "v3",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/spec/containers[]/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers[]/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"newDigest": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx@sha256:222222222222222222
|
||||
name: nginx-tagged
|
||||
- image: nginx@sha256:222222222222222222
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx@sha256:222222222222222222
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:222222222222222222
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
Digest: "sha256:222222222222222222",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/spec/containers/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"newImageAndDigest": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.7.9
|
||||
name: nginx-tagged
|
||||
- image: nginx:latest
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: nginx
|
||||
name: nginx-notag
|
||||
- image: nginx@sha256:111111111111111111
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: busybox@sha256:222222222222222222
|
||||
name: nginx-tagged
|
||||
- image: busybox@sha256:222222222222222222
|
||||
name: nginx-latest
|
||||
- image: foobar:1
|
||||
name: replaced-with-digest
|
||||
- image: postgres:1.8.0
|
||||
name: postgresdb
|
||||
initContainers:
|
||||
- image: busybox@sha256:222222222222222222
|
||||
name: nginx-notag
|
||||
- image: busybox@sha256:222222222222222222
|
||||
name: nginx-sha256
|
||||
- image: alpine:1.8.0
|
||||
name: init-alpine
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "busybox",
|
||||
Digest: "sha256:222222222222222222",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/spec/containers[]/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers[]/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
"emptyContainers": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
containers:
|
||||
`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
containers: []
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewTag: "v2",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/containers[]/image",
|
||||
// CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"tagWithBraces": {
|
||||
input: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: some.registry.io/my-image:{GENERATED_TAG}
|
||||
name: my-image
|
||||
`,
|
||||
expectedOutput: `
|
||||
group: apps
|
||||
apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: deploy1
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: some.registry.io/my-image:my-fixed-tag
|
||||
name: my-image
|
||||
`,
|
||||
filter: Filter{
|
||||
ImageTag: types.Image{
|
||||
Name: "some.registry.io/my-image",
|
||||
NewTag: "my-fixed-tag",
|
||||
},
|
||||
},
|
||||
fsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "spec/template/spec/containers[]/image",
|
||||
},
|
||||
{
|
||||
Path: "spec/template/spec/initContainers[]/image",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
filter := tc.filter
|
||||
filter.FsSlice = tc.fsSlice
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expectedOutput),
|
||||
strings.TrimSpace(filtertest.RunFilter(t, tc.input, filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package imagetag
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/internal/utils"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// LegacyFilter is an implementation of the kio.Filter interface
|
||||
// that scans through the provided kyaml data structure and updates
|
||||
// any values of any image fields that is inside a sequence under
|
||||
// a field called either containers or initContainers. The field is only
|
||||
// update if it has a value that matches and image reference and the name
|
||||
// of the image is a match with the provided ImageTag.
|
||||
type LegacyFilter struct {
|
||||
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
|
||||
}
|
||||
|
||||
var _ kio.Filter = LegacyFilter{}
|
||||
|
||||
func (lf LegacyFilter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(lf.filter)).Filter(nodes)
|
||||
}
|
||||
|
||||
func (lf LegacyFilter) filter(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
meta, err := node.GetMeta()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We do not make any changes if the type of the resource
|
||||
// is CustomResourceDefinition.
|
||||
if meta.Kind == `CustomResourceDefinition` {
|
||||
return node, nil
|
||||
}
|
||||
|
||||
fff := findFieldsFilter{
|
||||
fields: []string{"containers", "initContainers"},
|
||||
fieldCallback: checkImageTagsFn(lf.ImageTag),
|
||||
}
|
||||
if err := node.PipeE(fff); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
type fieldCallback func(node *yaml.RNode) error
|
||||
|
||||
// findFieldsFilter is an implementation of the kio.Filter
|
||||
// interface. It will walk the data structure and look for fields
|
||||
// that matches the provided list of field names. For each match,
|
||||
// the value of the field will be passed in as a parameter to the
|
||||
// provided fieldCallback.
|
||||
// TODO: move this to kyaml/filterutils
|
||||
type findFieldsFilter struct {
|
||||
fields []string
|
||||
|
||||
fieldCallback fieldCallback
|
||||
}
|
||||
|
||||
func (f findFieldsFilter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
||||
return obj, f.walk(obj)
|
||||
}
|
||||
|
||||
func (f findFieldsFilter) walk(node *yaml.RNode) error {
|
||||
switch node.YNode().Kind {
|
||||
case yaml.MappingNode:
|
||||
return node.VisitFields(func(n *yaml.MapNode) error {
|
||||
err := f.walk(n.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := n.Key.YNode().Value
|
||||
if utils.StringSliceContains(f.fields, key) {
|
||||
return f.fieldCallback(n.Value)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
case yaml.SequenceNode:
|
||||
return node.VisitElements(func(n *yaml.RNode) error {
|
||||
return f.walk(n)
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkImageTagsFn(imageTag types.Image) fieldCallback {
|
||||
return func(node *yaml.RNode) error {
|
||||
if node.YNode().Kind != yaml.SequenceNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
return node.VisitElements(func(n *yaml.RNode) error {
|
||||
// Look up any fields on the provided node that is named
|
||||
// image.
|
||||
return n.PipeE(yaml.Get("image"), imageTagUpdater{
|
||||
ImageTag: imageTag,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package imagetag
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
func TestLegacyImageTag_Filter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expectedOutput string
|
||||
filter LegacyFilter
|
||||
}{
|
||||
"updates multiple images inside containers": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.2.1
|
||||
- image: nginx:2.1.2
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: apache@12345
|
||||
- image: apache@12345
|
||||
`,
|
||||
filter: LegacyFilter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
Digest: "12345",
|
||||
},
|
||||
},
|
||||
},
|
||||
"updates inside both containers and initContainers": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.2.1
|
||||
- image: tomcat:1.2.3
|
||||
initContainers:
|
||||
- image: nginx:1.2.1
|
||||
- image: apache:1.2.3
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: apache:3.2.1
|
||||
- image: tomcat:1.2.3
|
||||
initContainers:
|
||||
- image: apache:3.2.1
|
||||
- image: apache:1.2.3
|
||||
`,
|
||||
filter: LegacyFilter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
NewTag: "3.2.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"updates on multiple depths": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx:1.2.1
|
||||
- image: tomcat:1.2.3
|
||||
template:
|
||||
spec:
|
||||
initContainers:
|
||||
- image: nginx:1.2.1
|
||||
- image: apache:1.2.3
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
containers:
|
||||
- image: apache:3.2.1
|
||||
- image: tomcat:1.2.3
|
||||
template:
|
||||
spec:
|
||||
initContainers:
|
||||
- image: apache:3.2.1
|
||||
- image: apache:1.2.3
|
||||
`,
|
||||
filter: LegacyFilter{
|
||||
ImageTag: types.Image{
|
||||
Name: "nginx",
|
||||
NewName: "apache",
|
||||
NewTag: "3.2.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
filter := tc.filter
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expectedOutput),
|
||||
strings.TrimSpace(filtertest.RunFilter(t, tc.input, filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package imagetag
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/image"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// imageTagUpdater is an implementation of the kio.Filter interface
|
||||
// that will update the value of the yaml node based on the provided
|
||||
// ImageTag if the current value matches the format of an image reference.
|
||||
type imageTagUpdater struct {
|
||||
Kind string `yaml:"kind,omitempty"`
|
||||
ImageTag types.Image `yaml:"imageTag,omitempty"`
|
||||
}
|
||||
|
||||
func (u imageTagUpdater) Filter(rn *yaml.RNode) (*yaml.RNode, error) {
|
||||
if err := yaml.ErrorIfInvalid(rn, yaml.ScalarNode); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
value := rn.YNode().Value
|
||||
|
||||
if !image.IsImageMatched(value, u.ImageTag.Name) {
|
||||
return rn, nil
|
||||
}
|
||||
|
||||
name, tag := image.Split(value)
|
||||
if u.ImageTag.NewName != "" {
|
||||
name = u.ImageTag.NewName
|
||||
}
|
||||
if u.ImageTag.NewTag != "" {
|
||||
tag = ":" + u.ImageTag.NewTag
|
||||
}
|
||||
if u.ImageTag.Digest != "" {
|
||||
tag = "@" + u.ImageTag.Digest
|
||||
}
|
||||
|
||||
return rn.Pipe(yaml.FieldSetter{StringValue: name + tag})
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package labels contains a kio.Filter implementation of the kustomize
|
||||
// labels transformer.
|
||||
package labels
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package labels
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
fss := builtinconfig.MakeDefaultConfig().CommonLabels
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`)}},
|
||||
Filters: []kio.Filter{Filter{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
FsSlice: fss,
|
||||
}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo
|
||||
// metadata:
|
||||
// name: instance
|
||||
// labels:
|
||||
// foo: bar
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// labels:
|
||||
// foo: bar
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package labels
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
type labelMap map[string]string
|
||||
|
||||
// Filter sets labels.
|
||||
type Filter struct {
|
||||
// Labels is the set of labels to apply to the inputs
|
||||
Labels labelMap `yaml:"labels,omitempty"`
|
||||
|
||||
// FsSlice identifies the label fields.
|
||||
FsSlice types.FsSlice
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
keys := yaml.SortedMapKeys(f.Labels)
|
||||
_, err := kio.FilterAll(yaml.FilterFunc(
|
||||
func(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
for _, k := range keys {
|
||||
if err := node.PipeE(fsslice.Filter{
|
||||
FsSlice: f.FsSlice,
|
||||
SetValue: filtersutil.SetEntry(
|
||||
k, f.Labels[k], yaml.NodeTagString),
|
||||
CreateKind: yaml.MappingNode, // Labels are MappingNodes.
|
||||
CreateTag: yaml.NodeTagMap,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return node, nil
|
||||
})).Filter(nodes)
|
||||
return nodes, err
|
||||
}
|
||||
@@ -1,413 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package labels
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
func TestLabels_Filter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expectedOutput string
|
||||
filter Filter
|
||||
}{
|
||||
"add": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
auto: ford
|
||||
bean: cannellini
|
||||
clown: emmett kelley
|
||||
dragon: smaug
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"clown": "emmett kelley",
|
||||
"auto": "ford",
|
||||
"dragon": "smaug",
|
||||
"bean": "cannellini",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"update": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: superman
|
||||
fiend: luthor
|
||||
bean: cannellini
|
||||
clown: emmett kelley
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"clown": "emmett kelley",
|
||||
"hero": "superman",
|
||||
"fiend": "luthor",
|
||||
"bean": "cannellini",
|
||||
}, FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"data-fieldspecs": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
sleater: kinney
|
||||
a:
|
||||
b:
|
||||
sleater: kinney
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
sleater: kinney
|
||||
a:
|
||||
b:
|
||||
sleater: kinney
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"sleater": "kinney",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
{
|
||||
Path: "a/b",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"fieldSpecWithKind": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v2
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
cheese: cheddar
|
||||
---
|
||||
apiVersion: example.com/v2
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
cheese: cheddar
|
||||
a:
|
||||
b:
|
||||
cheese: cheddar
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"cheese": "cheddar",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Kind: "Bar",
|
||||
},
|
||||
Path: "a/b",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"fieldSpecWithVersion": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v2
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
cheese: cheddar
|
||||
a:
|
||||
b:
|
||||
cheese: cheddar
|
||||
---
|
||||
apiVersion: example.com/v2
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
cheese: cheddar
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"cheese": "cheddar",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Version: "v1",
|
||||
},
|
||||
Path: "a/b",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"fieldSpecWithVersionInConfigButNoGroupInData": {
|
||||
input: `
|
||||
apiVersion: v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: v2
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
cheese: cheddar
|
||||
a:
|
||||
b:
|
||||
cheese: cheddar
|
||||
---
|
||||
apiVersion: v2
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
cheese: cheddar
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"cheese": "cheddar",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
{
|
||||
Gvk: resid.Gvk{
|
||||
Version: "v1",
|
||||
},
|
||||
Path: "a/b",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"number": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
1: emmett kelley
|
||||
auto: "2"
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"1": "emmett kelley",
|
||||
"auto": "2",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// test quoting of values which are not considered strings in yaml 1.1
|
||||
"yaml_1_1_compatibility": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
hero: batman
|
||||
fiend: riddler
|
||||
a: "y"
|
||||
b: y1
|
||||
c: "yes"
|
||||
d: yes1
|
||||
e: "true"
|
||||
f: true1
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"a": "y",
|
||||
"b": "y1",
|
||||
"c": "yes",
|
||||
"d": "yes1",
|
||||
"e": "true",
|
||||
"f": "true1",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"null_labels": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels: null
|
||||
`,
|
||||
expectedOutput: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
labels:
|
||||
a: a1
|
||||
`,
|
||||
filter: Filter{
|
||||
Labels: labelMap{
|
||||
"a": "a1",
|
||||
},
|
||||
FsSlice: []types.FieldSpec{
|
||||
{
|
||||
Path: "metadata/labels",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expectedOutput),
|
||||
strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// Package nameref contains a kio.Filter implementation of the kustomize
|
||||
// name reference transformer.
|
||||
package nameref
|
||||
@@ -1,396 +0,0 @@
|
||||
package nameref
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Filter updates a name references.
|
||||
type Filter struct {
|
||||
// Referrer refers to another resource X by X's name.
|
||||
// E.g. A Deployment can refer to a ConfigMap.
|
||||
// The Deployment is the Referrer,
|
||||
// the ConfigMap is the ReferralTarget.
|
||||
// This filter seeks to repair the reference in Deployment, given
|
||||
// that the ConfigMap's name may have changed.
|
||||
Referrer *resource.Resource
|
||||
|
||||
// NameFieldToUpdate is the field in the Referrer
|
||||
// that holds the name requiring an update.
|
||||
// This is the field to write.
|
||||
NameFieldToUpdate types.FieldSpec
|
||||
|
||||
// ReferralTarget is the source of the new value for
|
||||
// the name, always in the 'metadata/name' field.
|
||||
// This is the field to read.
|
||||
ReferralTarget resid.Gvk
|
||||
|
||||
// Set of resources to scan to find the ReferralTarget.
|
||||
ReferralCandidates resmap.ResMap
|
||||
}
|
||||
|
||||
// At time of writing, in practice this is called with a slice with only
|
||||
// one entry, the node also referred to be the resource in the Referrer field.
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(f.run)).Filter(nodes)
|
||||
}
|
||||
|
||||
// The node passed in here is the same node as held in Referrer;
|
||||
// that's how the referrer's name field is updated.
|
||||
// Currently, however, this filter still needs the extra methods on Referrer
|
||||
// to consult things like the resource Id, its namespace, etc.
|
||||
// TODO(3455): No filter should use the Resource api; all information
|
||||
// about names should come from annotations, with helper methods
|
||||
// on the RNode object. Resource should get stupider, RNode smarter.
|
||||
func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
if err := f.confirmNodeMatchesReferrer(node); err != nil {
|
||||
// sanity check.
|
||||
return nil, err
|
||||
}
|
||||
f.NameFieldToUpdate.Gvk = f.Referrer.GetGvk()
|
||||
if err := node.PipeE(fieldspec.Filter{
|
||||
FieldSpec: f.NameFieldToUpdate,
|
||||
SetValue: f.set,
|
||||
}); err != nil {
|
||||
return nil, errors.Wrapf(
|
||||
err, "updating name reference in '%s' field of '%s'",
|
||||
f.NameFieldToUpdate.Path, f.Referrer.CurId().String())
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// This function is called on the node found at FieldSpec.Path.
|
||||
// It's some node in the Referrer.
|
||||
func (f Filter) set(node *yaml.RNode) error {
|
||||
if yaml.IsMissingOrNull(node) {
|
||||
return nil
|
||||
}
|
||||
switch node.YNode().Kind {
|
||||
case yaml.ScalarNode:
|
||||
return f.setScalar(node)
|
||||
case yaml.MappingNode:
|
||||
return f.setMapping(node)
|
||||
case yaml.SequenceNode:
|
||||
return applyFilterToSeq(seqFilter{
|
||||
setScalarFn: f.setScalar,
|
||||
setMappingFn: f.setMapping,
|
||||
}, node)
|
||||
default:
|
||||
return fmt.Errorf("node must be a scalar, sequence or map")
|
||||
}
|
||||
}
|
||||
|
||||
// This method used when NameFieldToUpdate doesn't lead to
|
||||
// one scalar field (typically called 'name'), but rather
|
||||
// leads to a map field (called anything). In this case we
|
||||
// must complete the field path, looking for both a 'name'
|
||||
// and a 'namespace' field to help select the proper
|
||||
// ReferralTarget to read the name and namespace from.
|
||||
func (f Filter) setMapping(node *yaml.RNode) error {
|
||||
if node.YNode().Kind != yaml.MappingNode {
|
||||
return fmt.Errorf("expect a mapping node")
|
||||
}
|
||||
nameNode, err := node.Pipe(yaml.FieldMatcher{Name: "name"})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "trying to match 'name' field")
|
||||
}
|
||||
if nameNode == nil {
|
||||
// This is a _configuration_ error; the field path
|
||||
// specified in NameFieldToUpdate.Path doesn't resolve
|
||||
// to a map with a 'name' field, so we have no idea what
|
||||
// field to update with a new name.
|
||||
return fmt.Errorf("path config error; no 'name' field in node")
|
||||
}
|
||||
candidates, err := f.filterMapCandidatesByNamespace(node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldName := nameNode.YNode().Value
|
||||
referral, err := f.selectReferral(oldName, candidates)
|
||||
if err != nil || referral == nil {
|
||||
// Nil referral means nothing to do.
|
||||
return err
|
||||
}
|
||||
f.recordTheReferral(referral)
|
||||
if referral.GetName() == oldName && referral.GetNamespace() == "" {
|
||||
// The name has not changed, nothing to do.
|
||||
return nil
|
||||
}
|
||||
if err = node.PipeE(yaml.FieldSetter{
|
||||
Name: "name",
|
||||
StringValue: referral.GetName(),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if referral.GetNamespace() == "" {
|
||||
// Don't write an empty string into the namespace field, as
|
||||
// it should not replace the value "default". The empty
|
||||
// string is handled as a wild card here, not as an implicit
|
||||
// specification of the "default" k8s namespace.
|
||||
return nil
|
||||
}
|
||||
return node.PipeE(yaml.FieldSetter{
|
||||
Name: "namespace",
|
||||
StringValue: referral.GetNamespace(),
|
||||
})
|
||||
}
|
||||
|
||||
func (f Filter) filterMapCandidatesByNamespace(
|
||||
node *yaml.RNode) ([]*resource.Resource, error) {
|
||||
namespaceNode, err := node.Pipe(yaml.FieldMatcher{Name: "namespace"})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "trying to match 'namespace' field")
|
||||
}
|
||||
if namespaceNode == nil {
|
||||
return f.ReferralCandidates.Resources(), nil
|
||||
}
|
||||
namespace := namespaceNode.YNode().Value
|
||||
nsMap := f.ReferralCandidates.GroupedByOriginalNamespace()
|
||||
if candidates, ok := nsMap[namespace]; ok {
|
||||
return candidates, nil
|
||||
}
|
||||
nsMap = f.ReferralCandidates.GroupedByCurrentNamespace()
|
||||
// This could be nil, or an empty list.
|
||||
return nsMap[namespace], nil
|
||||
}
|
||||
|
||||
func (f Filter) setScalar(node *yaml.RNode) error {
|
||||
referral, err := f.selectReferral(
|
||||
node.YNode().Value, f.ReferralCandidates.Resources())
|
||||
if err != nil || referral == nil {
|
||||
// Nil referral means nothing to do.
|
||||
return err
|
||||
}
|
||||
f.recordTheReferral(referral)
|
||||
if referral.GetName() == node.YNode().Value {
|
||||
// The name has not changed, nothing to do.
|
||||
return nil
|
||||
}
|
||||
return node.PipeE(yaml.FieldSetter{StringValue: referral.GetName()})
|
||||
}
|
||||
|
||||
// In the resource, make a note that it is referred to by the Referrer.
|
||||
func (f Filter) recordTheReferral(referral *resource.Resource) {
|
||||
referral.AppendRefBy(f.Referrer.CurId())
|
||||
}
|
||||
|
||||
// getRoleRefGvk returns a Gvk in the roleRef field. Return error
|
||||
// if the roleRef, roleRef/apiGroup or roleRef/kind is missing.
|
||||
func getRoleRefGvk(n *resource.Resource) (*resid.Gvk, error) {
|
||||
roleRef, err := n.Pipe(yaml.Lookup("roleRef"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if roleRef.IsNil() {
|
||||
return nil, fmt.Errorf("roleRef cannot be found in %s", n.MustString())
|
||||
}
|
||||
apiGroup, err := roleRef.Pipe(yaml.Lookup("apiGroup"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if apiGroup.IsNil() {
|
||||
return nil, fmt.Errorf(
|
||||
"apiGroup cannot be found in roleRef %s", roleRef.MustString())
|
||||
}
|
||||
kind, err := roleRef.Pipe(yaml.Lookup("kind"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if kind.IsNil() {
|
||||
return nil, fmt.Errorf(
|
||||
"kind cannot be found in roleRef %s", roleRef.MustString())
|
||||
}
|
||||
return &resid.Gvk{
|
||||
Group: apiGroup.YNode().Value,
|
||||
Kind: kind.YNode().Value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// sieveFunc returns true if the resource argument satisfies some criteria.
|
||||
type sieveFunc func(*resource.Resource) bool
|
||||
|
||||
// doSieve uses a function to accept or ignore resources from a list.
|
||||
// If list is nil, returns immediately.
|
||||
// It's a filter obviously, but that term is overloaded here.
|
||||
func doSieve(list []*resource.Resource, fn sieveFunc) (s []*resource.Resource) {
|
||||
for _, r := range list {
|
||||
if fn(r) {
|
||||
s = append(s, r)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func acceptAll(r *resource.Resource) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func previousNameMatches(name string) sieveFunc {
|
||||
return func(r *resource.Resource) bool {
|
||||
for _, id := range r.PrevIds() {
|
||||
if id.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func previousIdSelectedByGvk(gvk *resid.Gvk) sieveFunc {
|
||||
return func(r *resource.Resource) bool {
|
||||
for _, id := range r.PrevIds() {
|
||||
if id.IsSelected(gvk) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// If the we are updating a 'roleRef/name' field, the 'apiGroup' and 'kind'
|
||||
// fields in the same 'roleRef' map must be considered.
|
||||
// If either object is cluster-scoped, there can be a referral.
|
||||
// E.g. a RoleBinding (which exists in a namespace) can refer
|
||||
// to a ClusterRole (cluster-scoped) object.
|
||||
// https://kubernetes.io/docs/reference/access-authn-authz/rbac/#role-and-clusterrole
|
||||
// Likewise, a ClusterRole can refer to a Secret (in a namespace).
|
||||
// Objects in different namespaces generally cannot refer to other
|
||||
// with some exceptions (e.g. RoleBinding and ServiceAccount are both
|
||||
// namespaceable, but the former can refer to accounts in other namespaces).
|
||||
func (f Filter) roleRefFilter() sieveFunc {
|
||||
if !strings.HasSuffix(f.NameFieldToUpdate.Path, "roleRef/name") {
|
||||
return acceptAll
|
||||
}
|
||||
roleRefGvk, err := getRoleRefGvk(f.Referrer)
|
||||
if err != nil {
|
||||
return acceptAll
|
||||
}
|
||||
return previousIdSelectedByGvk(roleRefGvk)
|
||||
}
|
||||
|
||||
func prefixSuffixEquals(other resource.ResCtx) sieveFunc {
|
||||
return func(r *resource.Resource) bool {
|
||||
return r.PrefixesSuffixesEquals(other)
|
||||
}
|
||||
}
|
||||
|
||||
func (f Filter) sameCurrentNamespaceAsReferrer() sieveFunc {
|
||||
referrerCurId := f.Referrer.CurId()
|
||||
if referrerCurId.IsClusterScoped() {
|
||||
// If the referrer is cluster-scoped, let anything through.
|
||||
return acceptAll
|
||||
}
|
||||
return func(r *resource.Resource) bool {
|
||||
if r.CurId().IsClusterScoped() {
|
||||
// Allow cluster-scoped through.
|
||||
return true
|
||||
}
|
||||
if r.GetKind() == "ServiceAccount" {
|
||||
// Allow service accounts through, even though they
|
||||
// are in a namespace. A RoleBinding in another namespace
|
||||
// can reference them.
|
||||
return true
|
||||
}
|
||||
return referrerCurId.IsNsEquals(r.CurId())
|
||||
}
|
||||
}
|
||||
|
||||
// selectReferral picks the best referral from a list of candidates.
|
||||
func (f Filter) selectReferral(
|
||||
// The name referral that may need to be updated.
|
||||
oldName string,
|
||||
candidates []*resource.Resource) (*resource.Resource, error) {
|
||||
candidates = doSieve(candidates, previousNameMatches(oldName))
|
||||
candidates = doSieve(candidates, previousIdSelectedByGvk(&f.ReferralTarget))
|
||||
candidates = doSieve(candidates, f.roleRefFilter())
|
||||
candidates = doSieve(candidates, f.sameCurrentNamespaceAsReferrer())
|
||||
if len(candidates) == 1 {
|
||||
return candidates[0], nil
|
||||
}
|
||||
candidates = doSieve(candidates, prefixSuffixEquals(f.Referrer))
|
||||
if len(candidates) == 1 {
|
||||
return candidates[0], nil
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
if allNamesAreTheSame(candidates) {
|
||||
// Just take the first one.
|
||||
return candidates[0], nil
|
||||
}
|
||||
ids := getIds(candidates)
|
||||
f.failureDetails(candidates)
|
||||
return nil, fmt.Errorf(" found multiple possible referrals: %s", ids)
|
||||
}
|
||||
|
||||
func (f Filter) failureDetails(resources []*resource.Resource) {
|
||||
fmt.Printf(
|
||||
"\n**** Too many possible referral targets to referrer:\n%s\n",
|
||||
f.Referrer.MustYaml())
|
||||
for i, r := range resources {
|
||||
fmt.Printf(
|
||||
"--- possible referral %d:\n%s", i, r.MustYaml())
|
||||
fmt.Println("------")
|
||||
}
|
||||
}
|
||||
|
||||
func allNamesAreTheSame(resources []*resource.Resource) bool {
|
||||
name := resources[0].GetName()
|
||||
for i := 1; i < len(resources); i++ {
|
||||
if name != resources[i].GetName() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getIds(rs []*resource.Resource) string {
|
||||
var result []string
|
||||
for _, r := range rs {
|
||||
result = append(result, r.CurId().String())
|
||||
}
|
||||
return strings.Join(result, ", ")
|
||||
}
|
||||
|
||||
func checkEqual(k, a, b string) error {
|
||||
if a != b {
|
||||
return fmt.Errorf(
|
||||
"node-referrerOriginal '%s' mismatch '%s' != '%s'",
|
||||
k, a, b)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f Filter) confirmNodeMatchesReferrer(node *yaml.RNode) error {
|
||||
meta, err := node.GetMeta()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gvk := f.Referrer.GetGvk()
|
||||
if err = checkEqual(
|
||||
"APIVersion", meta.APIVersion, gvk.ApiVersion()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = checkEqual(
|
||||
"Kind", meta.Kind, gvk.Kind); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = checkEqual(
|
||||
"Name", meta.Name, f.Referrer.GetName()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = checkEqual(
|
||||
"Namespace", meta.Namespace, f.Referrer.GetNamespace()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,789 +0,0 @@
|
||||
package nameref
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/provider"
|
||||
"sigs.k8s.io/kustomize/api/resmap"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
)
|
||||
|
||||
func TestNamerefFilter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
referrerOriginal string
|
||||
candidates string
|
||||
referrerFinal string
|
||||
filter Filter
|
||||
originalNames []string
|
||||
}{
|
||||
"simple scalar": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "newName2"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: newName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
"sequence": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
seq:
|
||||
- oldName1
|
||||
- oldName2
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName1", "newName2"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
seq:
|
||||
- newName
|
||||
- oldName2
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "seq"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
"mapping": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
map:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "newName2"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
map:
|
||||
name: newName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "map"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
"mapping with namespace": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
namespace: someNs
|
||||
map:
|
||||
name: oldName
|
||||
namespace: someNs
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
namespace: someNs
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: thirdName
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName", "oldName"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
namespace: someNs
|
||||
map:
|
||||
name: newName
|
||||
namespace: someNs
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "map"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
"null value": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
map:
|
||||
name: null
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: NotSecret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "newName2"},
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
map:
|
||||
name: null
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "map"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
factory := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||
referrer, err := factory.FromBytes([]byte(tc.referrerOriginal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tc.filter.Referrer = referrer
|
||||
|
||||
resMapFactory := resmap.NewFactory(factory)
|
||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||
tc.originalNames, []byte(tc.candidates))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
||||
tc.filter.ReferralCandidates = candidates
|
||||
|
||||
result := filtertest_test.RunFilter(t, tc.referrerOriginal, tc.filter)
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.referrerFinal),
|
||||
strings.TrimSpace(result)) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamerefFilterUnhappy(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
referrerOriginal string
|
||||
candidates string
|
||||
referrerFinal string
|
||||
filter Filter
|
||||
originalNames []string
|
||||
}{
|
||||
"multiple match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
"no name": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
notName: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
factory := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||
referrer, err := factory.FromBytes([]byte(tc.referrerOriginal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tc.filter.Referrer = referrer
|
||||
|
||||
resMapFactory := resmap.NewFactory(factory)
|
||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||
tc.originalNames, []byte(tc.candidates))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
||||
tc.filter.ReferralCandidates = candidates
|
||||
|
||||
_, err = filtertest_test.RunFilterE(t, tc.referrerOriginal, tc.filter)
|
||||
if err == nil {
|
||||
t.Fatalf("expect an error")
|
||||
}
|
||||
if tc.referrerFinal != "" && !assert.EqualError(t, err, tc.referrerFinal) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCandidatesWithDifferentPrefixSuffix(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
referrerOriginal string
|
||||
candidates string
|
||||
referrerFinal string
|
||||
filter Filter
|
||||
originalNames []string
|
||||
prefix []string
|
||||
suffix []string
|
||||
inputPrefix string
|
||||
inputSuffix string
|
||||
err bool
|
||||
}{
|
||||
"prefix match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix1", "prefix2"},
|
||||
suffix: []string{"", "suffix2"},
|
||||
inputPrefix: "prefix1",
|
||||
inputSuffix: "",
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: newName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
"suffix match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"", "prefix2"},
|
||||
suffix: []string{"suffix1", "suffix2"},
|
||||
inputPrefix: "",
|
||||
inputSuffix: "suffix1",
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: newName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
"prefix suffix both match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix1", "prefix2"},
|
||||
suffix: []string{"suffix1", "suffix2"},
|
||||
inputPrefix: "prefix1",
|
||||
inputSuffix: "suffix1",
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: newName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
"multiple match: both": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix", "prefix"},
|
||||
suffix: []string{"suffix", "suffix"},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "suffix",
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
"multiple match: only prefix": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix", "prefix"},
|
||||
suffix: []string{"", ""},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "",
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
"multiple match: only suffix": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"", ""},
|
||||
suffix: []string{"suffix", "suffix"},
|
||||
inputPrefix: "",
|
||||
inputSuffix: "suffix",
|
||||
referrerFinal: "",
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: true,
|
||||
},
|
||||
"no match: neither match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix1", "prefix2"},
|
||||
suffix: []string{"suffix1", "suffix2"},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "suffix",
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
"no match: prefix doesn't match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix1", "prefix2"},
|
||||
suffix: []string{"suffix", "suffix"},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "suffix",
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
"no match: suffix doesn't match": {
|
||||
referrerOriginal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
candidates: `
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: newName2
|
||||
`,
|
||||
originalNames: []string{"oldName", "oldName"},
|
||||
prefix: []string{"prefix", "prefix"},
|
||||
suffix: []string{"suffix1", "suffix2"},
|
||||
inputPrefix: "prefix",
|
||||
inputSuffix: "suffix",
|
||||
referrerFinal: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
ref:
|
||||
name: oldName
|
||||
`,
|
||||
filter: Filter{
|
||||
NameFieldToUpdate: types.FieldSpec{Path: "ref/name"},
|
||||
ReferralTarget: resid.Gvk{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Secret",
|
||||
},
|
||||
},
|
||||
err: false,
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
factory := provider.NewDefaultDepProvider().GetResourceFactory()
|
||||
referrer, err := factory.FromBytes([]byte(tc.referrerOriginal))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tc.inputPrefix != "" {
|
||||
referrer.AddNamePrefix(tc.inputPrefix)
|
||||
}
|
||||
if tc.inputSuffix != "" {
|
||||
referrer.AddNameSuffix(tc.inputSuffix)
|
||||
}
|
||||
tc.filter.Referrer = referrer
|
||||
|
||||
resMapFactory := resmap.NewFactory(factory)
|
||||
candidatesRes, err := factory.SliceFromBytesWithNames(
|
||||
tc.originalNames, []byte(tc.candidates))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for i := range candidatesRes {
|
||||
if tc.prefix[i] != "" {
|
||||
candidatesRes[i].AddNamePrefix(tc.prefix[i])
|
||||
}
|
||||
if tc.suffix[i] != "" {
|
||||
candidatesRes[i].AddNameSuffix(tc.suffix[i])
|
||||
}
|
||||
}
|
||||
|
||||
candidates := resMapFactory.FromResourceSlice(candidatesRes)
|
||||
tc.filter.ReferralCandidates = candidates
|
||||
|
||||
if !tc.err {
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.referrerFinal),
|
||||
strings.TrimSpace(
|
||||
filtertest_test.RunFilter(
|
||||
t, tc.referrerOriginal, tc.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
} else {
|
||||
_, err := filtertest_test.RunFilterE(
|
||||
t, tc.referrerOriginal, tc.filter)
|
||||
if err == nil {
|
||||
t.Fatalf("an error is expected")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package nameref
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
type setFn func(*yaml.RNode) error
|
||||
|
||||
type seqFilter struct {
|
||||
setScalarFn setFn
|
||||
setMappingFn setFn
|
||||
}
|
||||
|
||||
func (sf seqFilter) Filter(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
if yaml.IsMissingOrNull(node) {
|
||||
return node, nil
|
||||
}
|
||||
switch node.YNode().Kind {
|
||||
case yaml.ScalarNode:
|
||||
// Kind: Role/ClusterRole
|
||||
// FieldSpec is rules.resourceNames
|
||||
err := sf.setScalarFn(node)
|
||||
return node, err
|
||||
case yaml.MappingNode:
|
||||
// Kind: RoleBinding/ClusterRoleBinding
|
||||
// FieldSpec is subjects
|
||||
// Note: The corresponding fieldSpec had been changed from
|
||||
// from path: subjects/name to just path: subjects. This is
|
||||
// what get mutatefield to request the mapping of the whole
|
||||
// map containing namespace and name instead of just a simple
|
||||
// string field containing the name
|
||||
err := sf.setMappingFn(node)
|
||||
return node, err
|
||||
default:
|
||||
return node, fmt.Errorf(
|
||||
"%#v is expected to be either a string or a map of string", node)
|
||||
}
|
||||
}
|
||||
|
||||
// applyFilterToSeq will apply the filter to each element in the sequence node
|
||||
func applyFilterToSeq(filter yaml.Filter, node *yaml.RNode) error {
|
||||
if node.YNode().Kind != yaml.SequenceNode {
|
||||
return fmt.Errorf("expect a sequence node but got %v", node.YNode().Kind)
|
||||
}
|
||||
|
||||
for _, elem := range node.Content() {
|
||||
rnode := yaml.NewRNode(elem)
|
||||
err := rnode.PipeE(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package nameref
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func SeqFilter(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
if node.YNode().Value == "aaa" {
|
||||
node.YNode().SetString("ccc")
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func TestApplyFilterToSeq(t *testing.T) {
|
||||
fltr := yaml.FilterFunc(SeqFilter)
|
||||
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expect string
|
||||
}{
|
||||
"replace in seq": {
|
||||
input: `
|
||||
- aaa
|
||||
- bbb`,
|
||||
expect: `
|
||||
- ccc
|
||||
- bbb`,
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
node, err := yaml.Parse(tc.input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = applyFilterToSeq(fltr, node)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expect),
|
||||
strings.TrimSpace(node.MustString())) {
|
||||
t.Fatalf("expect:\n%s\nactual:\n%s",
|
||||
strings.TrimSpace(tc.expect),
|
||||
strings.TrimSpace(node.MustString()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyFilterToSeqUnhappy(t *testing.T) {
|
||||
fltr := yaml.FilterFunc(SeqFilter)
|
||||
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
}{
|
||||
"replace in seq": {
|
||||
input: `
|
||||
aaa`,
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
node, err := yaml.Parse(tc.input)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = applyFilterToSeq(fltr, node)
|
||||
if err == nil {
|
||||
t.Fatalf("expect an error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package namespace contains a kio.Filter implementation of the kustomize
|
||||
// namespace transformer.
|
||||
//
|
||||
// Special cases for known Kubernetes resources have been hardcoded in addition
|
||||
// to those defined by the FsSlice.
|
||||
package namespace
|
||||
@@ -1,50 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package namespace_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/namespace"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
fss := builtinconfig.MakeDefaultConfig().NameSpace
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: bar
|
||||
`)}},
|
||||
Filters: []kio.Filter{namespace.Filter{Namespace: "app", FsSlice: fss}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo
|
||||
// metadata:
|
||||
// name: instance
|
||||
// namespace: app
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// namespace: app
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package namespace
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
// Namespace is the namespace to apply to the inputs
|
||||
Namespace string `yaml:"namespace,omitempty"`
|
||||
|
||||
// FsSlice contains the FieldSpecs to locate the namespace field
|
||||
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
|
||||
func (ns Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(ns.run)).Filter(nodes)
|
||||
}
|
||||
|
||||
// Run runs the filter on a single node rather than a slice
|
||||
func (ns Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
// hacks for hardcoded types -- :(
|
||||
if err := ns.hacks(node); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Remove the fieldspecs that are for hardcoded fields. The fieldspecs
|
||||
// exist for backwards compatibility with other implementations
|
||||
// of this transformation.
|
||||
// This implementation of the namespace transformation
|
||||
// Does not use the fieldspecs for implementing cases which
|
||||
// require hardcoded logic.
|
||||
ns.FsSlice = ns.removeFieldSpecsForHacks(ns.FsSlice)
|
||||
|
||||
// transformations based on data -- :)
|
||||
err := node.PipeE(fsslice.Filter{
|
||||
FsSlice: ns.FsSlice,
|
||||
SetValue: filtersutil.SetScalar(ns.Namespace),
|
||||
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
||||
CreateTag: yaml.NodeTagString,
|
||||
})
|
||||
return node, err
|
||||
}
|
||||
|
||||
// hacks applies the namespace transforms that are hardcoded rather
|
||||
// than specified through FieldSpecs.
|
||||
func (ns Filter) hacks(obj *yaml.RNode) error {
|
||||
gvk := resid.GvkFromNode(obj)
|
||||
if err := ns.metaNamespaceHack(obj, gvk); err != nil {
|
||||
return err
|
||||
}
|
||||
return ns.roleBindingHack(obj, gvk)
|
||||
}
|
||||
|
||||
// metaNamespaceHack is a hack for implementing the namespace transform
|
||||
// for the metadata.namespace field on namespace scoped resources.
|
||||
// namespace scoped resources are determined by NOT being present
|
||||
// in a hard-coded list of cluster-scoped resource types (by apiVersion and kind).
|
||||
//
|
||||
// This hack should be updated to allow individual resources to specify
|
||||
// if they are cluster scoped through either an annotation on the resources,
|
||||
// or through inlined OpenAPI on the resource as a YAML comment.
|
||||
func (ns Filter) metaNamespaceHack(obj *yaml.RNode, gvk resid.Gvk) error {
|
||||
if gvk.IsClusterScoped() {
|
||||
return nil
|
||||
}
|
||||
f := fsslice.Filter{
|
||||
FsSlice: []types.FieldSpec{
|
||||
{Path: types.MetadataNamespacePath, CreateIfNotPresent: true},
|
||||
},
|
||||
SetValue: filtersutil.SetScalar(ns.Namespace),
|
||||
CreateKind: yaml.ScalarNode, // Namespace is a ScalarNode
|
||||
}
|
||||
_, err := f.Filter(obj)
|
||||
return err
|
||||
}
|
||||
|
||||
// roleBindingHack is a hack for implementing the namespace transform
|
||||
// for RoleBinding and ClusterRoleBinding resource types.
|
||||
// RoleBinding and ClusterRoleBinding have namespace set on
|
||||
// elements of the "subjects" field if and only if the subject elements
|
||||
// "name" is "default". Otherwise the namespace is not set.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// kind: RoleBinding
|
||||
// subjects:
|
||||
// - name: "default" # this will have the namespace set
|
||||
// ...
|
||||
// - name: "something-else" # this will not have the namespace set
|
||||
// ...
|
||||
func (ns Filter) roleBindingHack(obj *yaml.RNode, gvk resid.Gvk) error {
|
||||
if gvk.Kind != roleBindingKind && gvk.Kind != clusterRoleBindingKind {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lookup the namespace field on all elements.
|
||||
// We should change the fieldspec so this isn't necessary.
|
||||
obj, err := obj.Pipe(yaml.Lookup(subjectsField))
|
||||
if err != nil || yaml.IsMissingOrNull(obj) {
|
||||
return err
|
||||
}
|
||||
|
||||
// add the namespace to each "subject" with name: default
|
||||
err = obj.VisitElements(func(o *yaml.RNode) error {
|
||||
// The only case we need to force the namespace
|
||||
// if for the "service account". "default" is
|
||||
// kind of hardcoded here for right now.
|
||||
name, err := o.Pipe(
|
||||
yaml.Lookup("name"), yaml.Match("default"),
|
||||
)
|
||||
if err != nil || yaml.IsMissingOrNull(name) {
|
||||
return err
|
||||
}
|
||||
|
||||
// set the namespace for the default account
|
||||
v := yaml.NewScalarRNode(ns.Namespace)
|
||||
return o.PipeE(
|
||||
yaml.LookupCreate(yaml.ScalarNode, "namespace"),
|
||||
yaml.FieldSetter{Value: v},
|
||||
)
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// removeFieldSpecsForHacks removes from the list fieldspecs that
|
||||
// have hardcoded implementations
|
||||
func (ns Filter) removeFieldSpecsForHacks(fs types.FsSlice) types.FsSlice {
|
||||
var val types.FsSlice
|
||||
for i := range fs {
|
||||
// implemented by metaNamespaceHack
|
||||
if fs[i].Path == types.MetadataNamespacePath {
|
||||
continue
|
||||
}
|
||||
// implemented by roleBindingHack
|
||||
if fs[i].Kind == roleBindingKind && fs[i].Path == subjectsField {
|
||||
continue
|
||||
}
|
||||
// implemented by roleBindingHack
|
||||
if fs[i].Kind == clusterRoleBindingKind && fs[i].Path == subjectsField {
|
||||
continue
|
||||
}
|
||||
val = append(val, fs[i])
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
const (
|
||||
subjectsField = "subjects"
|
||||
roleBindingKind = "RoleBinding"
|
||||
clusterRoleBindingKind = "ClusterRoleBinding"
|
||||
)
|
||||
@@ -1,311 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package namespace_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/filters/namespace"
|
||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
var tests = []TestCase{
|
||||
{
|
||||
name: "add",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "foo"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "null_ns",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: null
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: null
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "foo"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "add-recurse",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
namespace: foo
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "foo"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
# update this namespace
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: bar
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
# update this namespace
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "foo"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update-rolebinding",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
namespace: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: bar
|
||||
metadata:
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: bar
|
||||
metadata:
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
metadata:
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: RoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
namespace: foo
|
||||
metadata:
|
||||
namespace: bar
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "bar"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "update-clusterrolebinding",
|
||||
input: `
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: foo
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
namespace: foo
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: default
|
||||
namespace: bar
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
subjects:
|
||||
- name: something
|
||||
namespace: foo
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "bar"},
|
||||
},
|
||||
|
||||
{
|
||||
name: "data-fieldspecs",
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
a:
|
||||
b:
|
||||
c: foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: foo
|
||||
a:
|
||||
b:
|
||||
c: foo
|
||||
`,
|
||||
filter: namespace.Filter{Namespace: "foo"},
|
||||
fsslice: []types.FieldSpec{
|
||||
{
|
||||
Path: "a/b/c",
|
||||
CreateIfNotPresent: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
filter namespace.Filter
|
||||
fsslice types.FsSlice
|
||||
}
|
||||
|
||||
var config = builtinconfig.MakeDefaultConfig()
|
||||
|
||||
func TestNamespace_Filter(t *testing.T) {
|
||||
for i := range tests {
|
||||
test := tests[i]
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
test.filter.FsSlice = append(config.NameSpace, test.fsslice...)
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(test.expected),
|
||||
strings.TrimSpace(
|
||||
filtertest_test.RunFilter(t, test.input, test.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package namespace contains a kio.Filter implementation of the kustomize
|
||||
// patchjson6902 transformer
|
||||
package patchjson6902
|
||||
@@ -1,55 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package patchjson6902
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
namespace: bar
|
||||
`)}},
|
||||
Filters: []kio.Filter{
|
||||
Filter{
|
||||
Patch: `
|
||||
- op: replace
|
||||
path: /metadata/namespace
|
||||
value: "ns"
|
||||
`,
|
||||
},
|
||||
},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo
|
||||
// metadata:
|
||||
// name: instance
|
||||
// namespace: ns
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: instance
|
||||
// namespace: ns
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package patchjson6902
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
k8syaml "sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
Patch string
|
||||
|
||||
decodedPatch jsonpatch.Patch
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
|
||||
func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
decodedPatch, err := pf.decodePatch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pf.decodedPatch = decodedPatch
|
||||
return kio.FilterAll(yaml.FilterFunc(pf.run)).Filter(nodes)
|
||||
}
|
||||
|
||||
func (pf Filter) decodePatch() (jsonpatch.Patch, error) {
|
||||
patch := pf.Patch
|
||||
// If the patch doesn't look like a JSON6902 patch, we
|
||||
// try to parse it to json.
|
||||
if !strings.HasPrefix(pf.Patch, "[") {
|
||||
p, err := k8syaml.YAMLToJSON([]byte(patch))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patch = string(p)
|
||||
}
|
||||
decodedPatch, err := jsonpatch.DecodePatch([]byte(patch))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return decodedPatch, nil
|
||||
}
|
||||
|
||||
func (pf Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
// We don't actually use the kyaml library for manipulating the
|
||||
// yaml here. We just marshal it to json and rely on the
|
||||
// jsonpatch library to take care of applying the patch.
|
||||
// This means ordering might not be preserved with this filter.
|
||||
b, err := node.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := pf.decodedPatch.Apply(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = node.UnmarshalJSON(res)
|
||||
return node, err
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package patchjson6902
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
)
|
||||
|
||||
const input = `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
`
|
||||
|
||||
func TestSomething(t *testing.T) {
|
||||
testCases := []struct {
|
||||
testName string
|
||||
input string
|
||||
filter Filter
|
||||
expectedOutput string
|
||||
}{
|
||||
{
|
||||
testName: "single operation, json",
|
||||
input: input,
|
||||
filter: Filter{
|
||||
Patch: `[
|
||||
{"op": "replace", "path": "/spec/replica", "value": 5}
|
||||
]`,
|
||||
},
|
||||
expectedOutput: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 5
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "multiple operations, json",
|
||||
input: input,
|
||||
filter: Filter{
|
||||
Patch: `[
|
||||
{"op": "replace", "path": "/spec/template/spec/containers/0/name", "value": "my-nginx"},
|
||||
{"op": "add", "path": "/spec/replica", "value": 999},
|
||||
{"op": "add", "path": "/spec/template/spec/containers/0/command", "value": ["arg1", "arg2", "arg3"]}
|
||||
]`,
|
||||
},
|
||||
expectedOutput: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 999
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- arg1
|
||||
- arg2
|
||||
- arg3
|
||||
image: nginx
|
||||
name: my-nginx
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "single operation, yaml",
|
||||
input: input,
|
||||
filter: Filter{
|
||||
Patch: `
|
||||
- op: replace
|
||||
path: /spec/replica
|
||||
value: 5
|
||||
`,
|
||||
},
|
||||
expectedOutput: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 5
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "multiple operations, yaml",
|
||||
input: input,
|
||||
filter: Filter{
|
||||
Patch: `
|
||||
- op: replace
|
||||
path: /spec/template/spec/containers/0/name
|
||||
value: my-nginx
|
||||
- op: add
|
||||
path: /spec/replica
|
||||
value: 999
|
||||
- op: add
|
||||
path: /spec/template/spec/containers/0/command
|
||||
value:
|
||||
- arg1
|
||||
- arg2
|
||||
- arg3
|
||||
`,
|
||||
},
|
||||
expectedOutput: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
replica: 999
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
old-label: old-value
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- arg1
|
||||
- arg2
|
||||
- arg3
|
||||
image: nginx
|
||||
name: my-nginx
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expectedOutput),
|
||||
strings.TrimSpace(
|
||||
filtertest.RunFilter(t, tc.input, tc.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package patchstrategicmerge contains a kio.Filter implementation of the
|
||||
// kustomize strategic merge patch transformer.
|
||||
package patchstrategicmerge
|
||||
@@ -1,49 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package patchstrategicmerge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
replicas: 3
|
||||
`)}},
|
||||
Filters: []kio.Filter{Filter{
|
||||
Patch: yaml.MustParse(`
|
||||
spec:
|
||||
template:
|
||||
containers:
|
||||
- image: nginx
|
||||
`),
|
||||
}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo
|
||||
// metadata:
|
||||
// name: instance
|
||||
// spec:
|
||||
// replicas: 3
|
||||
// template:
|
||||
// containers:
|
||||
// - image: nginx
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package patchstrategicmerge
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
|
||||
)
|
||||
|
||||
type Filter struct {
|
||||
Patch *yaml.RNode
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
|
||||
// Filter does a strategic merge patch, which can delete nodes.
|
||||
func (pf Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
var result []*yaml.RNode
|
||||
for i := range nodes {
|
||||
r, err := merge2.Merge(
|
||||
pf.Patch, nodes[i],
|
||||
yaml.MergeOptions{
|
||||
ListIncreaseDirection: yaml.MergeOptionsListPrepend,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if r != nil {
|
||||
result = append(result, r)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,749 +0,0 @@
|
||||
package patchstrategicmerge
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
patch *yaml.RNode
|
||||
expected string
|
||||
}{
|
||||
"simple": {
|
||||
input: `apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: clown
|
||||
spec:
|
||||
numReplicas: 1
|
||||
`,
|
||||
patch: yaml.MustParse(`apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: clown
|
||||
spec:
|
||||
numReplicas: 999
|
||||
`),
|
||||
expected: `apiVersion: v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: clown
|
||||
spec:
|
||||
numReplicas: 999
|
||||
`,
|
||||
},
|
||||
"nullMapEntry1": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: my-foo
|
||||
spec:
|
||||
bar:
|
||||
B:
|
||||
C: Z
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: my-foo
|
||||
spec:
|
||||
bar:
|
||||
C: Z
|
||||
D: W
|
||||
baz:
|
||||
hello: world
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: my-foo
|
||||
spec:
|
||||
bar:
|
||||
C: Z
|
||||
D: W
|
||||
baz:
|
||||
hello: world
|
||||
`,
|
||||
},
|
||||
"nullMapEntry2": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: my-foo
|
||||
spec:
|
||||
bar:
|
||||
C: Z
|
||||
D: W
|
||||
baz:
|
||||
hello: world
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: my-foo
|
||||
spec:
|
||||
bar:
|
||||
B:
|
||||
C: Z
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: my-foo
|
||||
spec:
|
||||
bar:
|
||||
C: Z
|
||||
D: W
|
||||
baz:
|
||||
hello: world
|
||||
`,
|
||||
},
|
||||
"simple patch": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
metadata:
|
||||
name: yourDeploy
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: yourDeploy
|
||||
kind: Deployment
|
||||
`,
|
||||
},
|
||||
"container patch": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo0
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo0
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
`,
|
||||
},
|
||||
"volumes patch": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
volumes:
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
volumes:
|
||||
- name: foo0
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
volumes:
|
||||
- name: foo0
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
`,
|
||||
},
|
||||
"nested patch": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
args:
|
||||
- abc
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
args:
|
||||
- def
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
metadata:
|
||||
name: myDeploy
|
||||
kind: Deployment
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
args:
|
||||
- def
|
||||
`,
|
||||
},
|
||||
"remove mapping - directive": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
$patch: delete
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers: []
|
||||
`,
|
||||
},
|
||||
"replace mapping - directive": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
$patch: replace
|
||||
containers:
|
||||
- name: new
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: new
|
||||
`,
|
||||
},
|
||||
"merge mapping - directive": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test1
|
||||
$patch: merge
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test1
|
||||
`,
|
||||
},
|
||||
"remove list - directive": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- whatever
|
||||
- $patch: delete
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec: {}
|
||||
`,
|
||||
},
|
||||
"replace list - directive": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: replace
|
||||
image: replace
|
||||
- $patch: replace
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: replace
|
||||
image: replace
|
||||
`,
|
||||
},
|
||||
"merge list - directive": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test2
|
||||
image: test2
|
||||
- $patch: merge
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: myDeploy
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: test2
|
||||
image: test2
|
||||
- name: test
|
||||
image: test
|
||||
`,
|
||||
},
|
||||
"list map keys - add a port, no names": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
- containerPort: 80
|
||||
protocol: UDP
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
- containerPort: 80
|
||||
protocol: UDP
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
`,
|
||||
},
|
||||
"list map keys - add name to port": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
name: UDP-name-patch
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
name: UDP-name-patch
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
`,
|
||||
},
|
||||
"list map keys - replace port name": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
name: UDP-name-original
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
name: TCP-name-original
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
name: UDP-name-patch
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
protocol: UDP
|
||||
name: UDP-name-patch
|
||||
- containerPort: 8080
|
||||
protocol: TCP
|
||||
name: TCP-name-original
|
||||
`,
|
||||
},
|
||||
"list map keys - add a port, no protocol": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 80
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: test-image
|
||||
name: test-deployment
|
||||
ports:
|
||||
- containerPort: 80
|
||||
- containerPort: 8080
|
||||
`,
|
||||
},
|
||||
|
||||
// Test for issue #3513
|
||||
// Currently broken; when one port has only containerPort, the output
|
||||
// should not merge containerPort 8301 together
|
||||
// This occurs because when protocol is missing on the first port,
|
||||
// the merge code uses [containerPort] as the merge key rather than
|
||||
// [containerPort, protocol]
|
||||
"list map keys - protocol only present on some ports": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: consul
|
||||
image: "dashicorp/consul:1.9.1"
|
||||
ports:
|
||||
- containerPort: 8500
|
||||
name: http
|
||||
- containerPort: 8301
|
||||
protocol: "TCP"
|
||||
- containerPort: 8301
|
||||
protocol: "UDP"
|
||||
`,
|
||||
patch: yaml.MustParse(`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
labels:
|
||||
test: label
|
||||
`),
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: test-deployment
|
||||
labels:
|
||||
test: label
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: consul
|
||||
image: "dashicorp/consul:1.9.1"
|
||||
ports:
|
||||
- containerPort: 8500
|
||||
name: http
|
||||
- containerPort: 8301
|
||||
protocol: "TCP"
|
||||
- containerPort: 8301
|
||||
protocol: "UDP"
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
f := Filter{
|
||||
Patch: tc.patch,
|
||||
}
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expected),
|
||||
strings.TrimSpace(
|
||||
filtertest.RunFilter(t, tc.input, f))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package prefixsuffix contains a kio.Filter implementation of the kustomize
|
||||
// PrefixSuffixTransformer.
|
||||
package prefixsuffix
|
||||
@@ -1,47 +0,0 @@
|
||||
// Copyright 2020 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package prefixsuffix_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
err := kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`)}},
|
||||
Filters: []kio.Filter{prefixsuffix.Filter{
|
||||
Prefix: "baz-", FieldSpec: types.FieldSpec{Path: "metadata/name"}}},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo
|
||||
// metadata:
|
||||
// name: baz-instance
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Bar
|
||||
// metadata:
|
||||
// name: baz-instance
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package prefixsuffix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Filter applies resource name prefix's and suffix's using the fieldSpecs
|
||||
type Filter struct {
|
||||
Prefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
|
||||
Suffix string `json:"suffix,omitempty" yaml:"suffix,omitempty"`
|
||||
|
||||
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
||||
}
|
||||
|
||||
var _ kio.Filter = Filter{}
|
||||
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(f.run)).Filter(nodes)
|
||||
}
|
||||
|
||||
func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
err := node.PipeE(fieldspec.Filter{
|
||||
FieldSpec: f.FieldSpec,
|
||||
SetValue: f.evaluateField,
|
||||
CreateKind: yaml.ScalarNode, // Name is a ScalarNode
|
||||
CreateTag: yaml.NodeTagString,
|
||||
})
|
||||
return node, err
|
||||
}
|
||||
|
||||
func (f Filter) evaluateField(node *yaml.RNode) error {
|
||||
return filtersutil.SetScalar(fmt.Sprintf(
|
||||
"%s%s%s", f.Prefix, node.YNode().Value, f.Suffix))(node)
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package prefixsuffix_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
var tests = map[string]TestCase{
|
||||
"prefix": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: foo-instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: foo-instance
|
||||
`,
|
||||
filter: prefixsuffix.Filter{Prefix: "foo-"},
|
||||
fs: types.FieldSpec{Path: "metadata/name"},
|
||||
},
|
||||
|
||||
"suffix": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance-foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance-foo
|
||||
`,
|
||||
filter: prefixsuffix.Filter{Suffix: "-foo"},
|
||||
fs: types.FieldSpec{Path: "metadata/name"},
|
||||
},
|
||||
|
||||
"prefix-suffix": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: bar-instance-foo
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: bar-instance-foo
|
||||
`,
|
||||
filter: prefixsuffix.Filter{Prefix: "bar-", Suffix: "-foo"},
|
||||
fs: types.FieldSpec{Path: "metadata/name"},
|
||||
},
|
||||
|
||||
"data-fieldspecs": {
|
||||
input: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: d
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: d
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo
|
||||
metadata:
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: foo-d
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Bar
|
||||
metadata:
|
||||
name: instance
|
||||
a:
|
||||
b:
|
||||
c: foo-d
|
||||
`,
|
||||
filter: prefixsuffix.Filter{Prefix: "foo-"},
|
||||
fs: types.FieldSpec{Path: "a/b/c"},
|
||||
},
|
||||
}
|
||||
|
||||
type TestCase struct {
|
||||
input string
|
||||
expected string
|
||||
filter prefixsuffix.Filter
|
||||
fs types.FieldSpec
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
for name := range tests {
|
||||
test := tests[name]
|
||||
t.Run(name, func(t *testing.T) {
|
||||
test.filter.FieldSpec = test.fs
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(test.expected),
|
||||
strings.TrimSpace(
|
||||
filtertest_test.RunFilter(t, test.input, test.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
// Package refvar contains a kio.Filter implementation of the kustomize
|
||||
// refvar transformer (find and replace $(FOO) style variables in strings).
|
||||
package refvar
|
||||
@@ -1,147 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package refvar
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
operator = '$'
|
||||
referenceOpener = '('
|
||||
referenceCloser = ')'
|
||||
)
|
||||
|
||||
// syntaxWrap returns the input string wrapped by the expansion syntax.
|
||||
func syntaxWrap(input string) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteByte(operator)
|
||||
sb.WriteByte(referenceOpener)
|
||||
sb.WriteString(input)
|
||||
sb.WriteByte(referenceCloser)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// MappingFunc maps a string to anything.
|
||||
type MappingFunc func(string) interface{}
|
||||
|
||||
// MakePrimitiveReplacer returns a MappingFunc that uses a map to do
|
||||
// replacements, and a histogram to count map hits.
|
||||
//
|
||||
// Func behavior:
|
||||
//
|
||||
// If the input key is NOT found in the map, the key is wrapped up as
|
||||
// as a variable declaration string and returned, e.g. key FOO becomes $(FOO).
|
||||
// This string is presumably put back where it was found, and might get replaced
|
||||
// later.
|
||||
//
|
||||
// If the key is found in the map, the value is returned if it is a primitive
|
||||
// type (string, bool, number), and the hit is counted.
|
||||
//
|
||||
// If it's not a primitive type (e.g. a map, struct, func, etc.) then this
|
||||
// function doesn't know what to do with it and it returns the key wrapped up
|
||||
// again as if it had not been replaced. This should probably be an error.
|
||||
func MakePrimitiveReplacer(
|
||||
counts map[string]int, someMap map[string]interface{}) MappingFunc {
|
||||
return func(key string) interface{} {
|
||||
if value, ok := someMap[key]; ok {
|
||||
switch typedV := value.(type) {
|
||||
case string, int, int32, int64, float32, float64, bool:
|
||||
counts[key]++
|
||||
return typedV
|
||||
default:
|
||||
// If the value is some complicated type (e.g. a map or struct),
|
||||
// this function doesn't know how to jam it into a string,
|
||||
// so just pretend it was a cache miss.
|
||||
// Likely this should be an error instead of a silent failure,
|
||||
// since the programmer passed an impossible value.
|
||||
log.Printf(
|
||||
"MakePrimitiveReplacer: bad replacement type=%T val=%v",
|
||||
typedV, typedV)
|
||||
return syntaxWrap(key)
|
||||
}
|
||||
}
|
||||
// If unable to return the mapped variable, return it
|
||||
// as it was found, and a later mapping might be able to
|
||||
// replace it.
|
||||
return syntaxWrap(key)
|
||||
}
|
||||
}
|
||||
|
||||
// DoReplacements replaces variable references in the input string
|
||||
// using the mapping function.
|
||||
func DoReplacements(input string, mapping MappingFunc) interface{} {
|
||||
var buf strings.Builder
|
||||
checkpoint := 0
|
||||
for cursor := 0; cursor < len(input); cursor++ {
|
||||
if input[cursor] == operator && cursor+1 < len(input) {
|
||||
// Copy the portion of the input string since the last
|
||||
// checkpoint into the buffer
|
||||
buf.WriteString(input[checkpoint:cursor])
|
||||
|
||||
// Attempt to read the variable name as defined by the
|
||||
// syntax from the input string
|
||||
read, isVar, advance := tryReadVariableName(input[cursor+1:])
|
||||
|
||||
if isVar {
|
||||
// We were able to read a variable name correctly;
|
||||
// apply the mapping to the variable name and copy the
|
||||
// bytes into the buffer
|
||||
mapped := mapping(read)
|
||||
if input == syntaxWrap(read) {
|
||||
// Preserve the type of variable
|
||||
return mapped
|
||||
}
|
||||
|
||||
// Variable is used in a middle of a string
|
||||
buf.WriteString(fmt.Sprintf("%v", mapped))
|
||||
} else {
|
||||
// Not a variable name; copy the read bytes into the buffer
|
||||
buf.WriteString(read)
|
||||
}
|
||||
|
||||
// Advance the cursor in the input string to account for
|
||||
// bytes consumed to read the variable name expression
|
||||
cursor += advance
|
||||
|
||||
// Advance the checkpoint in the input string
|
||||
checkpoint = cursor + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Return the buffer and any remaining unwritten bytes in the
|
||||
// input string.
|
||||
return buf.String() + input[checkpoint:]
|
||||
}
|
||||
|
||||
// tryReadVariableName attempts to read a variable name from the input
|
||||
// string and returns the content read from the input, whether that content
|
||||
// represents a variable name to perform mapping on, and the number of bytes
|
||||
// consumed in the input string.
|
||||
//
|
||||
// The input string is assumed not to contain the initial operator.
|
||||
func tryReadVariableName(input string) (string, bool, int) {
|
||||
switch input[0] {
|
||||
case operator:
|
||||
// Escaped operator; return it.
|
||||
return input[0:1], false, 1
|
||||
case referenceOpener:
|
||||
// Scan to expression closer
|
||||
for i := 1; i < len(input); i++ {
|
||||
if input[i] == referenceCloser {
|
||||
return input[1:i], true, i + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Incomplete reference; return it.
|
||||
return string(operator) + string(referenceOpener), false, 1
|
||||
default:
|
||||
// Not the beginning of an expression, ie, an operator
|
||||
// that doesn't begin an expression. Return the operator
|
||||
// and the first rune in the string.
|
||||
return string(operator) + string(input[0]), false, 1
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package refvar
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// Filter updates $(VAR) style variables with values.
|
||||
// The fieldSpecs are the places to look for occurrences of $(VAR).
|
||||
type Filter struct {
|
||||
MappingFunc MappingFunc `json:"mappingFunc,omitempty" yaml:"mappingFunc,omitempty"`
|
||||
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
||||
}
|
||||
|
||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
||||
return kio.FilterAll(yaml.FilterFunc(f.run)).Filter(nodes)
|
||||
}
|
||||
|
||||
func (f Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
||||
err := node.PipeE(fieldspec.Filter{
|
||||
FieldSpec: f.FieldSpec,
|
||||
SetValue: f.set,
|
||||
})
|
||||
return node, err
|
||||
}
|
||||
|
||||
func (f Filter) set(node *yaml.RNode) error {
|
||||
if yaml.IsMissingOrNull(node) {
|
||||
return nil
|
||||
}
|
||||
switch node.YNode().Kind {
|
||||
case yaml.ScalarNode:
|
||||
return f.setScalar(node)
|
||||
case yaml.MappingNode:
|
||||
return f.setMap(node)
|
||||
case yaml.SequenceNode:
|
||||
return f.setSeq(node)
|
||||
default:
|
||||
return fmt.Errorf("invalid type encountered %v", node.YNode().Kind)
|
||||
}
|
||||
}
|
||||
|
||||
func updateNodeValue(node *yaml.Node, newValue interface{}) {
|
||||
switch newValue := newValue.(type) {
|
||||
case int:
|
||||
node.Value = strconv.FormatInt(int64(newValue), 10)
|
||||
node.Tag = yaml.NodeTagInt
|
||||
case int32:
|
||||
node.Value = strconv.FormatInt(int64(newValue), 10)
|
||||
node.Tag = yaml.NodeTagInt
|
||||
case int64:
|
||||
node.Value = strconv.FormatInt(newValue, 10)
|
||||
node.Tag = yaml.NodeTagInt
|
||||
case bool:
|
||||
node.SetString(strconv.FormatBool(newValue))
|
||||
node.Tag = yaml.NodeTagBool
|
||||
case float32:
|
||||
node.SetString(strconv.FormatFloat(float64(newValue), 'f', -1, 32))
|
||||
node.Tag = yaml.NodeTagFloat
|
||||
case float64:
|
||||
node.SetString(strconv.FormatFloat(newValue, 'f', -1, 64))
|
||||
node.Tag = yaml.NodeTagFloat
|
||||
default:
|
||||
node.SetString(newValue.(string))
|
||||
node.Tag = yaml.NodeTagString
|
||||
}
|
||||
node.Style = 0
|
||||
}
|
||||
|
||||
func (f Filter) setScalar(node *yaml.RNode) error {
|
||||
if !yaml.IsYNodeString(node.YNode()) {
|
||||
return nil
|
||||
}
|
||||
v := DoReplacements(node.YNode().Value, f.MappingFunc)
|
||||
updateNodeValue(node.YNode(), v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f Filter) setMap(node *yaml.RNode) error {
|
||||
contents := node.YNode().Content
|
||||
for i := 0; i < len(contents); i += 2 {
|
||||
if !yaml.IsYNodeString(contents[i]) {
|
||||
return fmt.Errorf(
|
||||
"invalid map key: value='%s', tag='%s'",
|
||||
contents[i].Value, contents[i].Tag)
|
||||
}
|
||||
if !yaml.IsYNodeString(contents[i+1]) {
|
||||
continue
|
||||
}
|
||||
newValue := DoReplacements(contents[i+1].Value, f.MappingFunc)
|
||||
updateNodeValue(contents[i+1], newValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f Filter) setSeq(node *yaml.RNode) error {
|
||||
for _, item := range node.YNode().Content {
|
||||
if !yaml.IsYNodeString(item) {
|
||||
return fmt.Errorf("invalid value type expect a string")
|
||||
}
|
||||
newValue := DoReplacements(item.Value, f.MappingFunc)
|
||||
updateNodeValue(item, newValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,301 +0,0 @@
|
||||
package refvar_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
. "sigs.k8s.io/kustomize/api/filters/refvar"
|
||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
var makeMf = func(theMap map[string]interface{}) MappingFunc {
|
||||
ignored := make(map[string]int)
|
||||
return MakePrimitiveReplacer(ignored, theMap)
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expected string
|
||||
filter Filter
|
||||
}{
|
||||
"simple scalar": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: $(VAR)`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 5`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"VAR": int64(5),
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
||||
},
|
||||
},
|
||||
"non-string scalar": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 1`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 1`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"VAR": int64(5),
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
||||
},
|
||||
},
|
||||
"wrong path": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 1`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
spec:
|
||||
replicas: 1`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"VAR": int64(5),
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "a/b/c"},
|
||||
},
|
||||
},
|
||||
"sequence": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
- $(FOO)
|
||||
- $(BAR)
|
||||
- $(BAZ)
|
||||
- $(FOO)+$(BAR)
|
||||
- $(BOOL)
|
||||
- $(FLOAT)`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
- foo
|
||||
- bar
|
||||
- $(BAZ)
|
||||
- foo+bar
|
||||
- false
|
||||
- 1.23`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"FOO": "foo",
|
||||
"BAR": "bar",
|
||||
"BOOL": false,
|
||||
"FLOAT": 1.23,
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "data"},
|
||||
},
|
||||
},
|
||||
"maps": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
FOO: $(FOO)
|
||||
BAR: $(BAR)
|
||||
BAZ: $(BAZ)
|
||||
PLUS: $(FOO)+$(BAR)`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
FOO: foo
|
||||
BAR: bar
|
||||
BAZ: $(BAZ)
|
||||
PLUS: foo+bar`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"FOO": "foo",
|
||||
"BAR": "bar",
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "data"},
|
||||
},
|
||||
},
|
||||
"complicated case": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
slice1:
|
||||
- $(FOO)
|
||||
slice2:
|
||||
FOO: $(FOO)
|
||||
BAR: $(BAR)
|
||||
BOOL: false
|
||||
INT: 0
|
||||
SLICE:
|
||||
- $(FOO)`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
slice1:
|
||||
- $(FOO)
|
||||
slice2:
|
||||
FOO: foo
|
||||
BAR: bar
|
||||
BOOL: false
|
||||
INT: 0
|
||||
SLICE:
|
||||
- $(FOO)`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"FOO": "foo",
|
||||
"BAR": "bar",
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "data/slice2"},
|
||||
},
|
||||
},
|
||||
"null value": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
FOO: null`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
FOO: null`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
// no replacements!
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "data/FOO"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
if !assert.Equal(t,
|
||||
strings.TrimSpace(tc.expected),
|
||||
strings.TrimSpace(
|
||||
filtertest_test.RunFilter(t, tc.input, tc.filter))) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterUnhappy(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
input string
|
||||
expectedError string
|
||||
filter Filter
|
||||
}{
|
||||
"non-string in sequence": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
slice:
|
||||
- false`,
|
||||
expectedError: `considering field 'data/slice' of object
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
annotations:
|
||||
config.kubernetes.io/index: '0'
|
||||
internal.config.kubernetes.io/index: '0'
|
||||
data:
|
||||
slice:
|
||||
- false
|
||||
: invalid value type expect a string`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"VAR": int64(5),
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "data/slice"},
|
||||
},
|
||||
},
|
||||
"invalid key in map": {
|
||||
input: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dep
|
||||
data:
|
||||
1: str`,
|
||||
expectedError: `considering field 'data' of object
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
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 + `'`,
|
||||
filter: Filter{
|
||||
MappingFunc: makeMf(map[string]interface{}{
|
||||
"VAR": int64(5),
|
||||
}),
|
||||
FieldSpec: types.FieldSpec{Path: "data"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for tn, tc := range testCases {
|
||||
t.Run(tn, func(t *testing.T) {
|
||||
_, err := filtertest_test.RunFilterE(t, tc.input, tc.filter)
|
||||
if !assert.EqualError(t, err, tc.expectedError) {
|
||||
t.FailNow()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
// Package replacement contains a kio.Filter implementation of the kustomize
|
||||
// replacement transformer (accepts sources and looks for targets to replace
|
||||
// their values with values from the sources).
|
||||
package replacement
|
||||
@@ -1,68 +0,0 @@
|
||||
// Copyright 2021 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package replacement
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func ExampleFilter() {
|
||||
f := Filter{}
|
||||
err := yaml.Unmarshal([]byte(`
|
||||
replacements:
|
||||
- source:
|
||||
kind: Foo2
|
||||
fieldPath: spec.replicas
|
||||
targets:
|
||||
- select:
|
||||
kind: Foo1
|
||||
fieldPaths:
|
||||
- spec.replicas`), &f)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = kio.Pipeline{
|
||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo1
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
replicas: 3
|
||||
---
|
||||
apiVersion: example.com/v1
|
||||
kind: Foo2
|
||||
metadata:
|
||||
name: instance
|
||||
spec:
|
||||
replicas: 99
|
||||
`)}},
|
||||
Filters: []kio.Filter{f},
|
||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
||||
}.Execute()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Output:
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo1
|
||||
// metadata:
|
||||
// name: instance
|
||||
// spec:
|
||||
// replicas: 99
|
||||
// ---
|
||||
// apiVersion: example.com/v1
|
||||
// kind: Foo2
|
||||
// metadata:
|
||||
// name: instance
|
||||
// spec:
|
||||
// replicas: 99
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user