mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-29 09:40:49 +00:00
Compare commits
1 Commits
release-ap
...
kustomize/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c9d828f04 |
@@ -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.13
|
|
||||||
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.13
|
|
||||||
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.13
|
|
||||||
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.13
|
|
||||||
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
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -5,9 +5,6 @@
|
|||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
|
||||||
.idea
|
|
||||||
*.iml
|
|
||||||
|
|
||||||
# Test binary, build with `go test -c`
|
# Test binary, build with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
|
||||||
@@ -19,5 +16,3 @@
|
|||||||
|
|
||||||
# macOS
|
# macOS
|
||||||
*.DS_store
|
*.DS_store
|
||||||
|
|
||||||
.bin
|
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
run:
|
|
||||||
deadline: 5m
|
|
||||||
|
|
||||||
linters:
|
|
||||||
disable-all: true
|
|
||||||
enable:
|
|
||||||
- bodyclose
|
|
||||||
- deadcode
|
|
||||||
- depguard
|
|
||||||
# - dogsled
|
|
||||||
- dupl
|
|
||||||
# - errcheck
|
|
||||||
# - funlen
|
|
||||||
# - gochecknoinits
|
|
||||||
- goconst
|
|
||||||
# - gocritic
|
|
||||||
- gocyclo
|
|
||||||
- gofmt
|
|
||||||
- goimports
|
|
||||||
- golint
|
|
||||||
- gosec
|
|
||||||
- gosimple
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- interfacer
|
|
||||||
- lll
|
|
||||||
- misspell
|
|
||||||
- nakedret
|
|
||||||
# - scopelint
|
|
||||||
- staticcheck
|
|
||||||
- structcheck
|
|
||||||
# stylecheck demands that acronyms not be treated as words
|
|
||||||
# in camelCase, so JsonOp become JSONOp, etc. Yuck.
|
|
||||||
# - stylecheck
|
|
||||||
- typecheck
|
|
||||||
- unconvert
|
|
||||||
- unparam
|
|
||||||
- unused
|
|
||||||
- varcheck
|
|
||||||
# - whitespace
|
|
||||||
|
|
||||||
linters-settings:
|
|
||||||
dupl:
|
|
||||||
threshold: 400
|
|
||||||
lll:
|
|
||||||
line-length: 170
|
|
||||||
gocyclo:
|
|
||||||
min-complexity: 15
|
|
||||||
golint:
|
|
||||||
min-confidence: 0.85
|
|
||||||
30
.golangci.yml
Normal file
30
.golangci.yml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
run:
|
||||||
|
deadline: 5m
|
||||||
|
|
||||||
|
linters:
|
||||||
|
disable-all: true
|
||||||
|
enable:
|
||||||
|
- dupl
|
||||||
|
- goconst
|
||||||
|
- gocyclo
|
||||||
|
- gofmt
|
||||||
|
- golint
|
||||||
|
- govet
|
||||||
|
- ineffassign
|
||||||
|
- interfacer
|
||||||
|
- lll
|
||||||
|
- misspell
|
||||||
|
- nakedret
|
||||||
|
- structcheck
|
||||||
|
- unparam
|
||||||
|
- varcheck
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
dupl:
|
||||||
|
threshold: 400
|
||||||
|
lll:
|
||||||
|
line-length: 170
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 15
|
||||||
|
golint:
|
||||||
|
min-confidence: 0.85
|
||||||
38
.travis.yml
Normal file
38
.travis.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
# TODO: Uncomment when 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.12"
|
||||||
|
|
||||||
|
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:
|
||||||
|
- ./travis/pre-commit.sh
|
||||||
|
|
||||||
|
# TBD. Suppressing for now.
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
@@ -6,34 +6,16 @@ _As contributors and maintainers of this project, and in the interest of fosteri
|
|||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Dev guides:
|
|
||||||
|
|
||||||
- [Mac](docs/macDevGuide.md)
|
|
||||||
|
|
||||||
We have full documentation on how to get started contributing here:
|
We have full documentation on how to get started contributing here:
|
||||||
|
|
||||||
- [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
|
- [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)
|
- [Kubernetes Contributor Guide](http://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](http://git.k8s.io/community/contributors/guide#contributing)
|
||||||
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/README.md) - Common resources for existing developers
|
- [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet.md) - Common resources for existing developers
|
||||||
|
|
||||||
## Mentorship
|
## Mentorship
|
||||||
|
|
||||||
- [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - We have a diverse set of mentorship programs available that are always looking for volunteers!
|
- [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 generally follows the [Kubernetes Community Membership](https://github.com/kubernetes/community/blob/master/community-membership.md) contributor ladder. Roles are as follows:
|
|
||||||
|
|
||||||
1. Contributor: Anyone who actively contributes code, issues or reviews to the project. There are no Kustomize-specific requirements for this status. All contributors must [sign the CLA](https://github.com/kubernetes/community/tree/master/contributors/guide#prerequisites).
|
|
||||||
1. Member/Reviewer: All Kubernetes-SIGs org members have LGTM rights on the Kustomize repo. There are no Kustomize-specific requirements. Kustomize does not currently have any formal reviewers, but the role will be created if there is interest.
|
|
||||||
1. Maintainer/Approver: Highly experienced active reviewer and contributor to Kustomize. Has both LTGM and approval rights on the Kustomize repo, as well as [Github "maintain" rights](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization#repository-access-for-each-permission-level).
|
|
||||||
1. Admin/Owner: Maintainer who sets technical direction and makes or approves design decisions for the project. Has LGTM and approval rights on the Kustomize repo as well as [Github "admin" rights](https://docs.github.com/en/organizations/managing-access-to-your-organizations-repositories/repository-permission-levels-for-an-organization#repository-access-for-each-permission-level).
|
|
||||||
|
|
||||||
Administrative notes:
|
|
||||||
- Maintainers and admins must be added to the appropriate list both [in the Kustomize repo](https://github.com/kubernetes-sigs/kustomize/blob/8049f7b1af52e8a7ec26faf6cf714f560d0043c5/OWNERS_ALIASES) and [in the community repo](https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml). If this isn't done, the individual in question will lack either PR approval rights (Kustomize list) or the appropriate Github repository permissions (community list).
|
|
||||||
- The spec for the OWNERS file is [in the community repo](https://github.com/kubernetes/community/blob/master/contributors/guide/owners.md).
|
|
||||||
|
|
||||||
|
|
||||||
## Contact Information
|
## Contact Information
|
||||||
|
|
||||||
- [Slack channel](https://kubernetes.slack.com/messages/sig-cli)
|
- [Slack channel](https://kubernetes.slack.com/messages/sig-cli)
|
||||||
|
|||||||
377
Makefile
377
Makefile
@@ -1,366 +1,37 @@
|
|||||||
# Copyright 2019 The Kubernetes Authors.
|
BIN_NAME=kustomize
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
# Makefile for kustomize CLI and API.
|
|
||||||
|
|
||||||
SHELL := /usr/bin/env bash
|
COVER_FILE=coverage.out
|
||||||
MYGOBIN = $(shell go env GOBIN)
|
|
||||||
ifeq ($(MYGOBIN),)
|
|
||||||
MYGOBIN = $(shell go env GOPATH)/bin
|
|
||||||
endif
|
|
||||||
export PATH := $(MYGOBIN):$(PATH)
|
|
||||||
MODULES := '"cmd/config" "api/" "kustomize/" "kyaml/"'
|
|
||||||
|
|
||||||
# Provide defaults for REPO_OWNER and REPO_NAME if not present.
|
export GO111MODULE=on
|
||||||
# Typically these values would be provided by Prow.
|
|
||||||
ifndef REPO_OWNER
|
|
||||||
REPO_OWNER := "kubernetes-sigs"
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifndef REPO_NAME
|
all: test build
|
||||||
REPO_NAME := "kustomize"
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: all
|
test: generate-code test-lint test-go
|
||||||
all: install-tools verify-kustomize
|
|
||||||
|
|
||||||
.PHONY: verify-kustomize
|
test-go:
|
||||||
verify-kustomize: \
|
go test -v ./...
|
||||||
lint-kustomize \
|
|
||||||
test-unit-kustomize-all \
|
|
||||||
test-examples-kustomize-against-HEAD \
|
|
||||||
test-examples-kustomize-against-4.1
|
|
||||||
|
|
||||||
# The following target referenced by a file in
|
test-lint:
|
||||||
# https://github.com/kubernetes/test-infra/tree/master/config/jobs/kubernetes-sigs/kustomize
|
golangci-lint run ./...
|
||||||
.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-4.1
|
|
||||||
|
|
||||||
.PHONY: verify-kustomize-e2e
|
generate-code:
|
||||||
verify-kustomize-e2e: test-examples-e2e-kustomize
|
./plugin/generateBuiltins.sh $(GOPATH)
|
||||||
|
|
||||||
# Other builds in this repo might want a different linter version.
|
build:
|
||||||
# Without one Makefile to rule them all, the different makes
|
go build -o $(BIN_NAME) cmd/kustomize/main.go
|
||||||
# cannot assume that golanci-lint is at the version they want.
|
|
||||||
# This installs what kustomize wants to use.
|
|
||||||
$(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
|
|
||||||
|
|
||||||
$(MYGOBIN)/mdrip:
|
install:
|
||||||
go install github.com/monopole/mdrip@v1.0.2
|
go install $(PWD)/cmd/kustomize
|
||||||
|
|
||||||
$(MYGOBIN)/stringer:
|
cover:
|
||||||
go get golang.org/x/tools/cmd/stringer
|
# The plugin directory eludes coverage, and is therefore omitted
|
||||||
|
go test ./pkg/... ./k8sdeps/... ./internal/... -coverprofile=$(COVER_FILE) && \
|
||||||
|
go tool cover -html=$(COVER_FILE)
|
||||||
|
|
||||||
$(MYGOBIN)/goimports:
|
|
||||||
go get golang.org/x/tools/cmd/goimports
|
|
||||||
|
|
||||||
# Build from local source.
|
clean:
|
||||||
$(MYGOBIN)/gorepomod:
|
go clean
|
||||||
cd cmd/gorepomod; \
|
rm -f $(BIN_NAME)
|
||||||
go install .
|
rm -f $(COVER_FILE)
|
||||||
|
|
||||||
# Build from local source.
|
.PHONY: test build install clean generate-code test-go test-lint cover
|
||||||
$(MYGOBIN)/k8scopy:
|
|
||||||
cd cmd/k8scopy; \
|
|
||||||
go install .
|
|
||||||
|
|
||||||
# Build from local source.
|
|
||||||
$(MYGOBIN)/pluginator:
|
|
||||||
cd cmd/pluginator; \
|
|
||||||
go install .
|
|
||||||
|
|
||||||
# Build from local source.
|
|
||||||
$(MYGOBIN)/prchecker:
|
|
||||||
cd cmd/prchecker; \
|
|
||||||
go install .
|
|
||||||
|
|
||||||
# Build from local source.
|
|
||||||
$(MYGOBIN)/kustomize: build-kustomize-api
|
|
||||||
cd kustomize; \
|
|
||||||
go install .
|
|
||||||
|
|
||||||
.PHONY: install-tools
|
|
||||||
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.
|
|
||||||
#
|
|
||||||
# The rules to deal with builtin plugins are a bit
|
|
||||||
# complicated because
|
|
||||||
#
|
|
||||||
# - Every builtin plugin is a Go plugin -
|
|
||||||
# meaning it gets its own module directory
|
|
||||||
# (outside of the api module) with Go
|
|
||||||
# code in a 'main' package per Go plugin rules.
|
|
||||||
# - kustomize locates plugins using the
|
|
||||||
# 'apiVersion' and 'kind' fields from the
|
|
||||||
# plugin config file.
|
|
||||||
# - k8s wants CamelCase in 'kind' fields.
|
|
||||||
# - The module name (the last name in the path)
|
|
||||||
# must be the lowercased 'kind' of the
|
|
||||||
# plugin because Go and related tools
|
|
||||||
# demand lowercase in import paths, but
|
|
||||||
# allow CamelCase in file names.
|
|
||||||
# - the generated code must live in the api
|
|
||||||
# module (it's linked into the api).
|
|
||||||
|
|
||||||
# Where all generated builtin plugin code should go.
|
|
||||||
pGen=api/builtins
|
|
||||||
# Where the builtin Go plugin modules live.
|
|
||||||
pSrc=plugin/builtin
|
|
||||||
|
|
||||||
_builtinplugins = \
|
|
||||||
AnnotationsTransformer.go \
|
|
||||||
ConfigMapGenerator.go \
|
|
||||||
IAMPolicyGenerator.go \
|
|
||||||
HashTransformer.go \
|
|
||||||
ImageTagTransformer.go \
|
|
||||||
LabelTransformer.go \
|
|
||||||
LegacyOrderTransformer.go \
|
|
||||||
NamespaceTransformer.go \
|
|
||||||
PatchJson6902Transformer.go \
|
|
||||||
PatchStrategicMergeTransformer.go \
|
|
||||||
PatchTransformer.go \
|
|
||||||
PrefixSuffixTransformer.go \
|
|
||||||
ReplacementTransformer.go \
|
|
||||||
ReplicaCountTransformer.go \
|
|
||||||
SecretGenerator.go \
|
|
||||||
ValueAddTransformer.go \
|
|
||||||
HelmChartInflationGenerator.go
|
|
||||||
|
|
||||||
# Maintaining this explicit list of generated files, and
|
|
||||||
# adding it as a dependency to a few targets, to assure
|
|
||||||
# they get recreated if deleted. The rules below on how
|
|
||||||
# to make them don't, by themselves, assure they will be
|
|
||||||
# recreated if deleted.
|
|
||||||
builtinplugins = $(patsubst %,$(pGen)/%,$(_builtinplugins))
|
|
||||||
|
|
||||||
# These rules are verbose, but assure that if a source file
|
|
||||||
# is modified, the corresponding generated file, and only
|
|
||||||
# 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)/LabelTransformer.go: $(pSrc)/labeltransformer/LabelTransformer.go
|
|
||||||
$(pGen)/LegacyOrderTransformer.go: $(pSrc)/legacyordertransformer/LegacyOrderTransformer.go
|
|
||||||
$(pGen)/NamespaceTransformer.go: $(pSrc)/namespacetransformer/NamespaceTransformer.go
|
|
||||||
$(pGen)/PatchJson6902Transformer.go: $(pSrc)/patchjson6902transformer/PatchJson6902Transformer.go
|
|
||||||
$(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))))))))))))))))))))))))))
|
|
||||||
|
|
||||||
$(pGen)/%.go: $(MYGOBIN)/pluginator
|
|
||||||
@echo "generating $*"
|
|
||||||
( \
|
|
||||||
set -e; \
|
|
||||||
cd $(pSrc)/$(call toLowerCase,$*); \
|
|
||||||
go generate .; \
|
|
||||||
cd ../../../$(pGen); \
|
|
||||||
$(MYGOBIN)/goimports -w $*.go \
|
|
||||||
)
|
|
||||||
|
|
||||||
# Target is for debugging.
|
|
||||||
.PHONY: generate-kustomize-builtin-plugins
|
|
||||||
generate-kustomize-builtin-plugins: $(builtinplugins)
|
|
||||||
|
|
||||||
.PHONY: build-kustomize-external-go-plugin
|
|
||||||
build-kustomize-external-go-plugin:
|
|
||||||
./hack/buildExternalGoPlugins.sh ./plugin
|
|
||||||
|
|
||||||
.PHONY: clean-kustomize-external-go-plugin
|
|
||||||
clean-kustomize-external-go-plugin:
|
|
||||||
./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 ./...
|
|
||||||
|
|
||||||
.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"
|
|
||||||
|
|
||||||
.PHONY: test-unit-kustomize-plugins
|
|
||||||
test-unit-kustomize-plugins:
|
|
||||||
./hack/testUnitKustomizePlugins.sh
|
|
||||||
|
|
||||||
.PHONY: test-unit-kustomize-cli
|
|
||||||
test-unit-kustomize-cli:
|
|
||||||
cd kustomize; go test ./...
|
|
||||||
|
|
||||||
.PHONY: test-unit-kustomize-all
|
|
||||||
test-unit-kustomize-all: \
|
|
||||||
test-unit-kustomize-api \
|
|
||||||
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-4.1: $(MYGOBIN)/mdrip
|
|
||||||
./hack/testExamplesAgainstKustomize.sh v4@v4.1.2
|
|
||||||
|
|
||||||
# linux only.
|
|
||||||
# This is for testing an example plugin that
|
|
||||||
# uses kubeval for validation.
|
|
||||||
# Don't want to add a hard dependence in go.mod file
|
|
||||||
# to github.com/instrumenta/kubeval.
|
|
||||||
# Instead, download the binary.
|
|
||||||
$(MYGOBIN)/kubeval:
|
|
||||||
( \
|
|
||||||
set -e; \
|
|
||||||
d=$(shell mktemp -d); cd $$d; \
|
|
||||||
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:
|
|
||||||
( \
|
|
||||||
set -e; \
|
|
||||||
d=$(shell mktemp -d); cd $$d; \
|
|
||||||
tgzFile=helm-v2.13.1-linux-amd64.tar.gz; \
|
|
||||||
wget https://storage.googleapis.com/kubernetes-helm/$$tgzFile; \
|
|
||||||
tar -xvzf $$tgzFile; \
|
|
||||||
mv linux-amd64/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.5.3-linux-amd64.tar.gz; \
|
|
||||||
wget https://get.helm.sh/$$tgzFile; \
|
|
||||||
tar -xvzf $$tgzFile; \
|
|
||||||
mv linux-amd64/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-$(shell uname)-amd64; \
|
|
||||||
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_linux_amd64.tar.gz; \
|
|
||||||
wget https://github.com/cli/cli/releases/download/v1.0.0/$$tgzFile; \
|
|
||||||
tar -xvzf $$tgzFile; \
|
|
||||||
mv gh_1.0.0_linux_amd64/bin/gh $(MYGOBIN)/gh; \
|
|
||||||
rm -rf $$d \
|
|
||||||
)
|
|
||||||
|
|
||||||
.PHONY: clean
|
|
||||||
clean: clean-kustomize-external-go-plugin
|
|
||||||
go clean --cache
|
|
||||||
rm -f $(builtinplugins)
|
|
||||||
rm -f $(MYGOBIN)/goimports
|
|
||||||
rm -f $(MYGOBIN)/golangci-lint-kustomize
|
|
||||||
rm -f $(MYGOBIN)/kustomize
|
|
||||||
rm -f $(MYGOBIN)/mdrip
|
|
||||||
rm -f $(MYGOBIN)/prchecker
|
|
||||||
rm -f $(MYGOBIN)/stringer
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
# Keep *-admins and *-maintainers list in sync with corresponding lists in
|
|
||||||
# https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml
|
|
||||||
aliases:
|
aliases:
|
||||||
kustomize-admins:
|
kustomize-admins:
|
||||||
- knverey
|
|
||||||
- monopole
|
- monopole
|
||||||
- pwittrock
|
- pwittrock
|
||||||
kustomize-maintainers:
|
kustomize-maintainers:
|
||||||
|
- droot
|
||||||
- justinsb
|
- justinsb
|
||||||
- mortent
|
|
||||||
- natasha41575
|
|
||||||
- phanimarupaka
|
|
||||||
- Shell32-Natsu
|
|
||||||
emeritus-maintainers:
|
|
||||||
- liujingfang1
|
- liujingfang1
|
||||||
- mengqiy
|
- mengqiy
|
||||||
|
- monopole
|
||||||
|
- pwittrock
|
||||||
|
|||||||
90
README.md
90
README.md
@@ -9,37 +9,30 @@ patch [kubernetes style] API objects. It's like
|
|||||||
[`make`], in that what it does is declared in a file,
|
[`make`], in that what it does is declared in a file,
|
||||||
and it's like [`sed`], in that it emits edited text.
|
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)
|
[](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
|
## kubectl integration
|
||||||
|
|
||||||
The kustomize build flow at [v2.0.3] was added
|
Since [v1.14][kubectl announcement] the kustomize build system has been included in kubectl.
|
||||||
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.
|
|
||||||
|
|
||||||
| Kubectl version | Kustomize version |
|
| kubectl version | kustomize version |
|
||||||
| --- | --- |
|
|---------|--------|
|
||||||
| < v1.14 | n/a |
|
| v1.16.0 | [v2.0.3](https://github.com/kubernetes-sigs/kustomize/tree/v2.0.3) |
|
||||||
| v1.14-v1.20 | v2.0.3 |
|
| v1.15.x | [v2.0.3](https://github.com/kubernetes-sigs/kustomize/tree/v2.0.3) |
|
||||||
| v1.21 | v4.0.5 |
|
| v1.14.x | [v2.0.3](https://github.com/kubernetes-sigs/kustomize/tree/v2.0.3) |
|
||||||
|
|
||||||
[v2.0.3]: /../../tree/v2.0.3
|
For examples and guides for using the kubectl integration please see the [kubectl book] or the [kubernetes documentation].
|
||||||
[#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].
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -114,7 +107,7 @@ Take the work from step (1) above, move it into a
|
|||||||
`someApp` subdirectory called `base`, then
|
`someApp` subdirectory called `base`, then
|
||||||
place overlays in a sibling directory.
|
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
|
the base, and referring to patches to apply to that
|
||||||
base.
|
base.
|
||||||
|
|
||||||
@@ -140,8 +133,20 @@ The YAML can be directly [applied] to a cluster:
|
|||||||
|
|
||||||
## Community
|
## Community
|
||||||
|
|
||||||
- [file a bug](https://kubernetes-sigs.github.io/kustomize/contributing/bugs/) instructions
|
To file bugs please read [this](docs/bugs.md).
|
||||||
- [contribute a feature](https://kubernetes-sigs.github.io/kustomize/contributing/features/) instructions
|
|
||||||
|
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
|
### Code of conduct
|
||||||
|
|
||||||
@@ -150,27 +155,32 @@ is governed by the [Kubernetes Code of Conduct].
|
|||||||
|
|
||||||
[`make`]: https://www.gnu.org/software/make
|
[`make`]: https://www.gnu.org/software/make
|
||||||
[`sed`]: https://www.gnu.org/software/sed
|
[`sed`]: https://www.gnu.org/software/sed
|
||||||
[DAM]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#declarative-application-management
|
[DAM]: docs/glossary.md#declarative-application-management
|
||||||
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/0008-kustomize.md
|
[KEP]: https://github.com/kubernetes/enhancements/blob/master/keps/sig-cli/0008-kustomize.md
|
||||||
[Kubernetes Code of Conduct]: code-of-conduct.md
|
[Kubernetes Code of Conduct]: code-of-conduct.md
|
||||||
[applied]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#apply
|
[Mailing List]: https://groups.google.com/forum/#!forum/kubernetes-sig-cli
|
||||||
[base]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#base
|
[Slack]: https://kubernetes.slack.com/messages/sig-cli
|
||||||
[declarative configuration]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#declarative-application-management
|
[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
|
[imageBase]: docs/images/base.jpg
|
||||||
[imageOverlay]: docs/images/overlay.jpg
|
[imageOverlay]: docs/images/overlay.jpg
|
||||||
|
[kind/feature]: https://github.com/kubernetes-sigs/kustomize/labels/kind%2Ffeature
|
||||||
[kubectl announcement]: https://kubernetes.io/blog/2019/03/25/kubernetes-1-14-release-announcement
|
[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 documentation]: https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/
|
||||||
[kubernetes style]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#kubernetes-style-object
|
[kubernetes style]: docs/glossary.md#kubernetes-style-object
|
||||||
[kustomization]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#kustomization
|
[kustomization]: docs/glossary.md#kustomization
|
||||||
[overlay]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#overlay
|
[overlay]: docs/glossary.md#overlay
|
||||||
[overlays]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#overlay
|
[overlays]: docs/glossary.md#overlay
|
||||||
[release page]: https://github.com/kubernetes-sigs/kustomize/releases
|
[release page]: https://github.com/kubernetes-sigs/kustomize/releases
|
||||||
[resource]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#resource
|
[resource]: docs/glossary.md#resource
|
||||||
[resources]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#resource
|
[resources]: docs/glossary.md#resource
|
||||||
[sig-cli]: https://github.com/kubernetes/community/blob/master/sig-cli/README.md
|
[sig-cli]: https://github.com/kubernetes/community/blob/master/sig-cli/README.md
|
||||||
[variant]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#variant
|
[variant]: docs/glossary.md#variant
|
||||||
[variants]: https://kubernetes-sigs.github.io/kustomize/api-reference/glossary#variant
|
[variants]: docs/glossary.md#variant
|
||||||
[v2.0.3]: https://github.com/kubernetes-sigs/kustomize/releases/tag/v2.0.3
|
[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
|
[v2.1.0]: https://github.com/kubernetes-sigs/kustomize/releases/tag/v2.1.0
|
||||||
[workflows]: https://kubernetes-sigs.github.io/kustomize/guides
|
[workflows]: docs/workflows.md
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
// Code generated by pluginator on AnnotationsTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/annotations"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Add the given annotations to the given field specifications.
|
|
||||||
type AnnotationsTransformerPlugin struct {
|
|
||||||
Annotations map[string]string `json:"annotations,omitempty" yaml:"annotations,omitempty"`
|
|
||||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *AnnotationsTransformerPlugin) Config(
|
|
||||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
p.Annotations = nil
|
|
||||||
p.FieldSpecs = nil
|
|
||||||
return yaml.Unmarshal(c, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *AnnotationsTransformerPlugin) Transform(m resmap.ResMap) error {
|
|
||||||
if len(p.Annotations) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return m.ApplyFilter(annotations.Filter{
|
|
||||||
Annotations: p.Annotations,
|
|
||||||
FsSlice: p.FieldSpecs,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAnnotationsTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &AnnotationsTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// Code generated by pluginator on ConfigMapGenerator; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sigs.k8s.io/kustomize/api/kv"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigMapGeneratorPlugin struct {
|
|
||||||
h *resmap.PluginHelpers
|
|
||||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
|
||||||
types.ConfigMapArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConfigMapGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
|
|
||||||
p.ConfigMapArgs = types.ConfigMapArgs{}
|
|
||||||
err = yaml.Unmarshal(config, p)
|
|
||||||
if p.ConfigMapArgs.Name == "" {
|
|
||||||
p.ConfigMapArgs.Name = p.Name
|
|
||||||
}
|
|
||||||
if p.ConfigMapArgs.Namespace == "" {
|
|
||||||
p.ConfigMapArgs.Namespace = p.Namespace
|
|
||||||
}
|
|
||||||
p.h = h
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConfigMapGeneratorPlugin) Generate() (resmap.ResMap, error) {
|
|
||||||
return p.h.ResmapFactory().FromConfigMapArgs(
|
|
||||||
kv.NewLoader(p.h.Loader(), p.h.Validator()), p.ConfigMapArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfigMapGeneratorPlugin() resmap.GeneratorPlugin {
|
|
||||||
return &ConfigMapGeneratorPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// Code generated by pluginator on HashTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/ifc"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HashTransformerPlugin struct {
|
|
||||||
hasher ifc.KustHasher
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *HashTransformerPlugin) Config(
|
|
||||||
h *resmap.PluginHelpers, _ []byte) (err error) {
|
|
||||||
p.hasher = h.ResmapFactory().RF().Hasher()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transform appends hash to generated resources.
|
|
||||||
func (p *HashTransformerPlugin) Transform(m resmap.ResMap) error {
|
|
||||||
for _, res := range m.Resources() {
|
|
||||||
if res.NeedHashSuffix() {
|
|
||||||
h, err := res.Hash(p.hasher)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
res.StorePreviousId()
|
|
||||||
res.SetName(fmt.Sprintf("%s-%s", res.GetName(), h))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHashTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &HashTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -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{}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
// Code generated by pluginator on ImageTagTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/imagetag"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Find matching image declarations and replace
|
|
||||||
// the name, tag and/or digest.
|
|
||||||
type ImageTagTransformerPlugin struct {
|
|
||||||
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
|
|
||||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ImageTagTransformerPlugin) Config(
|
|
||||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
p.ImageTag = types.Image{}
|
|
||||||
p.FieldSpecs = nil
|
|
||||||
return yaml.Unmarshal(c, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ImageTagTransformerPlugin) Transform(m resmap.ResMap) error {
|
|
||||||
if err := m.ApplyFilter(imagetag.LegacyFilter{
|
|
||||||
ImageTag: p.ImageTag,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return m.ApplyFilter(imagetag.Filter{
|
|
||||||
ImageTag: p.ImageTag,
|
|
||||||
FsSlice: p.FieldSpecs,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewImageTagTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &ImageTagTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
// Code generated by pluginator on LabelTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/labels"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Add the given labels to the given field specifications.
|
|
||||||
type LabelTransformerPlugin struct {
|
|
||||||
Labels map[string]string `json:"labels,omitempty" yaml:"labels,omitempty"`
|
|
||||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *LabelTransformerPlugin) Config(
|
|
||||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
p.Labels = nil
|
|
||||||
p.FieldSpecs = nil
|
|
||||||
return yaml.Unmarshal(c, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *LabelTransformerPlugin) Transform(m resmap.ResMap) error {
|
|
||||||
if len(p.Labels) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return m.ApplyFilter(labels.Filter{
|
|
||||||
Labels: p.Labels,
|
|
||||||
FsSlice: p.FieldSpecs,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLabelTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &LabelTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
// Code generated by pluginator on NamespaceTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/namespace"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Change or set the namespace of non-cluster level resources.
|
|
||||||
type NamespaceTransformerPlugin struct {
|
|
||||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
|
||||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NamespaceTransformerPlugin) Config(
|
|
||||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
p.Namespace = ""
|
|
||||||
p.FieldSpecs = nil
|
|
||||||
return yaml.Unmarshal(c, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NamespaceTransformerPlugin) Transform(m resmap.ResMap) error {
|
|
||||||
if len(p.Namespace) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, r := range m.Resources() {
|
|
||||||
if r.IsNilOrEmpty() {
|
|
||||||
// Don't mutate empty objects?
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r.StorePreviousId()
|
|
||||||
if err := r.ApplyFilter(namespace.Filter{
|
|
||||||
Namespace: p.Namespace,
|
|
||||||
FsSlice: p.FieldSpecs,
|
|
||||||
}); 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 nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewNamespaceTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &NamespaceTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
// Code generated by pluginator on PatchJson6902Transformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
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/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PatchJson6902TransformerPlugin) Config(
|
|
||||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
p.ldr = h.Loader()
|
|
||||||
err = yaml.Unmarshal(c, p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if p.Target.Name == "" {
|
|
||||||
return fmt.Errorf("must specify the target name")
|
|
||||||
}
|
|
||||||
if p.Path == "" && p.JsonOp == "" {
|
|
||||||
return fmt.Errorf("empty file path and empty jsonOp")
|
|
||||||
}
|
|
||||||
if p.Path != "" {
|
|
||||||
if p.JsonOp != "" {
|
|
||||||
return fmt.Errorf("must specify a file path or jsonOp, not both")
|
|
||||||
}
|
|
||||||
rawOp, err := p.ldr.Load(p.Path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.JsonOp = string(rawOp)
|
|
||||||
if p.JsonOp == "" {
|
|
||||||
return fmt.Errorf("patch file '%s' empty seems to be empty", p.Path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if p.JsonOp[0] != '[' {
|
|
||||||
// if it doesn't seem to be JSON, imagine
|
|
||||||
// it is YAML, and convert to JSON.
|
|
||||||
op, err := yaml.YAMLToJSON([]byte(p.JsonOp))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.JsonOp = string(op)
|
|
||||||
}
|
|
||||||
p.decodedPatch, err = jsonpatch.DecodePatch([]byte(p.JsonOp))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "decoding %s", p.JsonOp)
|
|
||||||
}
|
|
||||||
if len(p.decodedPatch) == 0 {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"patch appears to be empty; file=%s, JsonOp=%s", p.Path, p.JsonOp)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, res := range resources {
|
|
||||||
err = res.ApplyFilter(patchjson6902.Filter{
|
|
||||||
Patch: p.JsonOp,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPatchJson6902TransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &PatchJson6902TransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
// Code generated by pluginator on PatchStrategicMergeTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PatchStrategicMergeTransformerPlugin struct {
|
|
||||||
loadedPatches []*resource.Resource
|
|
||||||
Paths []types.PatchStrategicMerge `json:"paths,omitempty" yaml:"paths,omitempty"`
|
|
||||||
Patches string `json:"patches,omitempty" yaml:"patches,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PatchStrategicMergeTransformerPlugin) Config(
|
|
||||||
h *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
err = yaml.Unmarshal(c, p)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(p.Paths) == 0 && p.Patches == "" {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
p.loadedPatches = append(p.loadedPatches, patches...)
|
|
||||||
}
|
|
||||||
if p.Patches != "" {
|
|
||||||
patches, err := h.ResmapFactory().RF().SliceFromBytes([]byte(p.Patches))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.loadedPatches = append(p.loadedPatches, patches...)
|
|
||||||
}
|
|
||||||
if len(p.loadedPatches) == 0 {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PatchStrategicMergeTransformerPlugin) Transform(m resmap.ResMap) error {
|
|
||||||
for _, patch := range p.loadedPatches {
|
|
||||||
target, err := m.GetById(patch.OrgId())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = m.ApplySmPatch(
|
|
||||||
resource.MakeIdSet([]*resource.Resource{target}), patch); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPatchStrategicMergeTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &PatchStrategicMergeTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
// Code generated by pluginator on PatchTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/patchjson6902"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/resource"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PatchTransformerPlugin struct {
|
|
||||||
loadedPatch *resource.Resource
|
|
||||||
decodedPatch jsonpatch.Patch
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.Patch = strings.TrimSpace(p.Patch)
|
|
||||||
if p.Patch == "" && p.Path == "" {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"must specify one of patch and path in\n%s", string(c))
|
|
||||||
}
|
|
||||||
if p.Patch != "" && p.Path != "" {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"patch and path can't be set at the same time\n%s", string(c))
|
|
||||||
}
|
|
||||||
if p.Path != "" {
|
|
||||||
loaded, loadErr := h.Loader().Load(p.Path)
|
|
||||||
if loadErr != nil {
|
|
||||||
return loadErr
|
|
||||||
}
|
|
||||||
p.Patch = string(loaded)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
if errSM != nil && errJson != nil {
|
|
||||||
return fmt.Errorf(
|
|
||||||
"unable to parse SM or JSON patch from [%v]", p.Patch)
|
|
||||||
}
|
|
||||||
if errSM == nil {
|
|
||||||
p.loadedPatch = patchSM
|
|
||||||
if p.Options["allowNameChange"] {
|
|
||||||
p.loadedPatch.AllowNameChange()
|
|
||||||
}
|
|
||||||
if p.Options["allowKindChange"] {
|
|
||||||
p.loadedPatch.AllowKindChange()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.decodedPatch = patchJson
|
|
||||||
}
|
|
||||||
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 err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return target.ApplySmPatch(patch)
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
err = res.ApplyFilter(patchjson6902.Filter{
|
|
||||||
Patch: p.Patch,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// jsonPatchFromBytes loads a Json 6902 patch from
|
|
||||||
// a bytes input
|
|
||||||
func jsonPatchFromBytes(
|
|
||||||
in []byte) (jsonpatch.Patch, error) {
|
|
||||||
ops := string(in)
|
|
||||||
if ops == "" {
|
|
||||||
return nil, fmt.Errorf("empty json patch operations")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ops[0] != '[' {
|
|
||||||
jsonOps, err := yaml.YAMLToJSON(in)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ops = string(jsonOps)
|
|
||||||
}
|
|
||||||
return jsonpatch.DecodePatch([]byte(ops))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPatchTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &PatchTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
// Code generated by pluginator on PrefixSuffixTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/prefixsuffix"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
|
||||||
"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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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"}},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixSuffixTransformerPlugin) Config(
|
|
||||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
p.Prefix = ""
|
|
||||||
p.Suffix = ""
|
|
||||||
p.FieldSpecs = nil
|
|
||||||
err = yaml.Unmarshal(c, p)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if p.FieldSpecs == nil {
|
|
||||||
return errors.New("fieldSpecs is not expected to be nil")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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()) {
|
|
||||||
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) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// TODO: move this test into the filter.
|
|
||||||
if smellsLikeANameChange(&fs) {
|
|
||||||
// "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 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func smellsLikeANameChange(fs *types.FieldSpec) bool {
|
|
||||||
return fs.Path == "metadata/name"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *PrefixSuffixTransformerPlugin) shouldSkip(id resid.ResId) bool {
|
|
||||||
for _, path := range prefixSuffixFieldSpecsToSkip {
|
|
||||||
if id.IsSelected(&path.Gvk) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
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{}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
// Code generated by pluginator on ReplicaCountTransformer; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/replicacount"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Find matching replicas declarations and replace the count.
|
|
||||||
// Eases the kustomization configuration of replica changes.
|
|
||||||
type ReplicaCountTransformerPlugin struct {
|
|
||||||
Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"`
|
|
||||||
FieldSpecs []types.FieldSpec `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ReplicaCountTransformerPlugin) Config(
|
|
||||||
_ *resmap.PluginHelpers, c []byte) (err error) {
|
|
||||||
p.Replica = types.Replica{}
|
|
||||||
p.FieldSpecs = nil
|
|
||||||
return yaml.Unmarshal(c, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
gvks := make([]string, len(p.FieldSpecs))
|
|
||||||
for i, replicaSpec := range p.FieldSpecs {
|
|
||||||
gvks[i] = replicaSpec.Gvk.String()
|
|
||||||
}
|
|
||||||
return fmt.Errorf("resource with name %s does not match a config with the following GVK %v",
|
|
||||||
p.Replica.Name, gvks)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match Replica.Name and FieldSpec
|
|
||||||
func (p *ReplicaCountTransformerPlugin) createMatcher(fs types.FieldSpec) resmap.IdMatcher {
|
|
||||||
return func(r resid.ResId) bool {
|
|
||||||
return r.Name == p.Replica.Name && r.Gvk.IsSelected(&fs.Gvk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewReplicaCountTransformerPlugin() resmap.TransformerPlugin {
|
|
||||||
return &ReplicaCountTransformerPlugin{}
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
// Code generated by pluginator on SecretGenerator; DO NOT EDIT.
|
|
||||||
// pluginator {unknown 1970-01-01T00:00:00Z }
|
|
||||||
|
|
||||||
package builtins
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sigs.k8s.io/kustomize/api/kv"
|
|
||||||
"sigs.k8s.io/kustomize/api/resmap"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SecretGeneratorPlugin struct {
|
|
||||||
h *resmap.PluginHelpers
|
|
||||||
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
|
||||||
types.SecretArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SecretGeneratorPlugin) Config(h *resmap.PluginHelpers, config []byte) (err error) {
|
|
||||||
p.SecretArgs = types.SecretArgs{}
|
|
||||||
err = yaml.Unmarshal(config, p)
|
|
||||||
if p.SecretArgs.Name == "" {
|
|
||||||
p.SecretArgs.Name = p.Name
|
|
||||||
}
|
|
||||||
if p.SecretArgs.Namespace == "" {
|
|
||||||
p.SecretArgs.Namespace = p.Namespace
|
|
||||||
}
|
|
||||||
p.h = h
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SecretGeneratorPlugin) Generate() (resmap.ResMap, error) {
|
|
||||||
return p.h.ResmapFactory().FromSecretArgs(
|
|
||||||
kv.NewLoader(p.h.Loader(), p.h.Validator()), p.SecretArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSecretGeneratorPlugin() resmap.GeneratorPlugin {
|
|
||||||
return &SecretGeneratorPlugin{}
|
|
||||||
}
|
|
||||||
@@ -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,8 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// Package builtins holds code generated from the builtin plugins.
|
|
||||||
// The "builtin" plugins are written as normal plugins and can
|
|
||||||
// be used as such, but they are also used to generate the code
|
|
||||||
// in this package so they can be statically linked to client code.
|
|
||||||
package builtins
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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,395 +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
|
|
||||||
}
|
|
||||||
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,299 +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'
|
|
||||||
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'
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
// Copyright 2021 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package replacement
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/internal/utils"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Filter struct {
|
|
||||||
Replacements []types.Replacement `json:"replacements,omitempty" yaml:"replacements,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter replaces values of targets with values from sources
|
|
||||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|
||||||
for _, r := range f.Replacements {
|
|
||||||
if r.Source == nil || r.Targets == nil {
|
|
||||||
return nil, fmt.Errorf("replacements must specify a source and at least one target")
|
|
||||||
}
|
|
||||||
value, err := getReplacement(nodes, &r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
nodes, err = applyReplacement(nodes, value, r.Targets)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nodes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyReplacement(nodes []*yaml.RNode, value *yaml.RNode, targets []*types.TargetSelector) ([]*yaml.RNode, error) {
|
|
||||||
for _, t := range targets {
|
|
||||||
if t.Select == nil {
|
|
||||||
return nil, fmt.Errorf("target must specify resources to select")
|
|
||||||
}
|
|
||||||
if len(t.FieldPaths) == 0 {
|
|
||||||
t.FieldPaths = []string{types.DefaultReplacementFieldPath}
|
|
||||||
}
|
|
||||||
for _, n := range nodes {
|
|
||||||
ids, err := utils.MakeResIds(n)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, id := range ids {
|
|
||||||
if id.IsSelectedBy(t.Select.ResId) && !rejectId(t.Reject, &id) {
|
|
||||||
err := applyToNode(n, value, t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nodes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func rejectId(rejects []*types.Selector, id *resid.ResId) bool {
|
|
||||||
for _, r := range rejects {
|
|
||||||
if id.IsSelectedBy(r.ResId) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func applyToNode(node *yaml.RNode, value *yaml.RNode, target *types.TargetSelector) error {
|
|
||||||
for _, fp := range target.FieldPaths {
|
|
||||||
fieldPath := utils.SmarterPathSplitter(fp, ".")
|
|
||||||
var t *yaml.RNode
|
|
||||||
var err error
|
|
||||||
if target.Options != nil && target.Options.Create {
|
|
||||||
t, err = node.Pipe(yaml.LookupCreate(value.YNode().Kind, fieldPath...))
|
|
||||||
} else {
|
|
||||||
t, err = node.Pipe(yaml.Lookup(fieldPath...))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if t != nil {
|
|
||||||
if err = setTargetValue(target.Options, t, value); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setTargetValue(options *types.FieldOptions, t *yaml.RNode, value *yaml.RNode) error {
|
|
||||||
value = value.Copy()
|
|
||||||
if options != nil && options.Delimiter != "" {
|
|
||||||
if t.YNode().Kind != yaml.ScalarNode {
|
|
||||||
return fmt.Errorf("delimiter option can only be used with scalar nodes")
|
|
||||||
}
|
|
||||||
tv := strings.Split(t.YNode().Value, options.Delimiter)
|
|
||||||
v := yaml.GetValue(value)
|
|
||||||
// TODO: Add a way to remove an element
|
|
||||||
switch {
|
|
||||||
case options.Index < 0: // prefix
|
|
||||||
tv = append([]string{v}, tv...)
|
|
||||||
case options.Index >= len(tv): // suffix
|
|
||||||
tv = append(tv, v)
|
|
||||||
default: // replace an element
|
|
||||||
tv[options.Index] = v
|
|
||||||
}
|
|
||||||
value.YNode().Value = strings.Join(tv, options.Delimiter)
|
|
||||||
}
|
|
||||||
t.SetYNode(value.YNode())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getReplacement(nodes []*yaml.RNode, r *types.Replacement) (*yaml.RNode, error) {
|
|
||||||
source, err := selectSourceNode(nodes, r.Source)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.Source.FieldPath == "" {
|
|
||||||
r.Source.FieldPath = types.DefaultReplacementFieldPath
|
|
||||||
}
|
|
||||||
fieldPath := utils.SmarterPathSplitter(r.Source.FieldPath, ".")
|
|
||||||
|
|
||||||
rn, err := source.Pipe(yaml.Lookup(fieldPath...))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !rn.IsNilOrEmpty() {
|
|
||||||
return getRefinedValue(r.Source.Options, rn)
|
|
||||||
}
|
|
||||||
return rn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRefinedValue(options *types.FieldOptions, rn *yaml.RNode) (*yaml.RNode, error) {
|
|
||||||
if options == nil || options.Delimiter == "" {
|
|
||||||
return rn, nil
|
|
||||||
}
|
|
||||||
if rn.YNode().Kind != yaml.ScalarNode {
|
|
||||||
return nil, fmt.Errorf("delimiter option can only be used with scalar nodes")
|
|
||||||
}
|
|
||||||
value := strings.Split(yaml.GetValue(rn), options.Delimiter)
|
|
||||||
if options.Index >= len(value) || options.Index < 0 {
|
|
||||||
return nil, fmt.Errorf("options.index %d is out of bounds for value %s", options.Index, yaml.GetValue(rn))
|
|
||||||
}
|
|
||||||
n := rn.Copy()
|
|
||||||
n.YNode().Value = value[options.Index]
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// selectSourceNode finds the node that matches the selector, returning
|
|
||||||
// an error if multiple or none are found
|
|
||||||
func selectSourceNode(nodes []*yaml.RNode, selector *types.SourceSelector) (*yaml.RNode, error) {
|
|
||||||
var matches []*yaml.RNode
|
|
||||||
for _, n := range nodes {
|
|
||||||
ids, err := utils.MakeResIds(n)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, id := range ids {
|
|
||||||
if id.IsSelectedBy(selector.ResId) {
|
|
||||||
if len(matches) > 0 {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"multiple matches for selector %s", selector)
|
|
||||||
}
|
|
||||||
matches = append(matches, n)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(matches) == 0 {
|
|
||||||
return nil, fmt.Errorf("nothing selected by %s", selector)
|
|
||||||
}
|
|
||||||
return matches[0], nil
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// Package replicacount contains a kio.Filter implementation of the kustomize
|
|
||||||
// ReplicaCountTransformer.
|
|
||||||
package replicacount
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package replicacount
|
|
||||||
|
|
||||||
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:
|
|
||||||
template:
|
|
||||||
replicas: 5
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
replicas: 5
|
|
||||||
`)}},
|
|
||||||
Filters: []kio.Filter{Filter{
|
|
||||||
Replica: types.Replica{
|
|
||||||
Count: 42,
|
|
||||||
Name: "instance",
|
|
||||||
},
|
|
||||||
FieldSpec: types.FieldSpec{
|
|
||||||
Path: "spec/template/replicas",
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
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:
|
|
||||||
// template:
|
|
||||||
// replicas: 42
|
|
||||||
// ---
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Bar
|
|
||||||
// metadata:
|
|
||||||
// name: instance
|
|
||||||
// spec:
|
|
||||||
// template:
|
|
||||||
// replicas: 42
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package replicacount
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"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 updates/sets replicas fields using the fieldSpecs
|
|
||||||
type Filter struct {
|
|
||||||
Replica types.Replica `json:"replica,omitempty" yaml:"replica,omitempty"`
|
|
||||||
FieldSpec types.FieldSpec `json:"fieldSpec,omitempty" yaml:"fieldSpec,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ kio.Filter = Filter{}
|
|
||||||
|
|
||||||
func (rc Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|
||||||
return kio.FilterAll(yaml.FilterFunc(rc.run)).Filter(nodes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rc Filter) run(node *yaml.RNode) (*yaml.RNode, error) {
|
|
||||||
err := node.PipeE(fieldspec.Filter{
|
|
||||||
FieldSpec: rc.FieldSpec,
|
|
||||||
SetValue: rc.set,
|
|
||||||
CreateKind: yaml.ScalarNode, // replicas is a ScalarNode
|
|
||||||
CreateTag: yaml.NodeTagInt,
|
|
||||||
})
|
|
||||||
return node, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rc Filter) set(node *yaml.RNode) error {
|
|
||||||
return filtersutil.SetScalar(strconv.FormatInt(rc.Replica.Count, 10))(node)
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
package replicacount
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFilter(t *testing.T) {
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input string
|
|
||||||
expected string
|
|
||||||
filter Filter
|
|
||||||
}{
|
|
||||||
"update field": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: dep
|
|
||||||
spec:
|
|
||||||
replicas: 5
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: dep
|
|
||||||
spec:
|
|
||||||
replicas: 42
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
Replica: types.Replica{
|
|
||||||
Name: "dep",
|
|
||||||
Count: 42,
|
|
||||||
},
|
|
||||||
FieldSpec: types.FieldSpec{Path: "spec/replicas"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"add field": {
|
|
||||||
input: `
|
|
||||||
apiVersion: custom/v1
|
|
||||||
kind: Custom
|
|
||||||
metadata:
|
|
||||||
name: cus
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
other: something
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: custom/v1
|
|
||||||
kind: Custom
|
|
||||||
metadata:
|
|
||||||
name: cus
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
other: something
|
|
||||||
replicas: 42
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
Replica: types.Replica{
|
|
||||||
Name: "cus",
|
|
||||||
Count: 42,
|
|
||||||
},
|
|
||||||
FieldSpec: types.FieldSpec{
|
|
||||||
Path: "spec/template/replicas",
|
|
||||||
CreateIfNotPresent: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"add_field_null": {
|
|
||||||
input: `
|
|
||||||
apiVersion: custom/v1
|
|
||||||
kind: Custom
|
|
||||||
metadata:
|
|
||||||
name: cus
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
other: something
|
|
||||||
replicas: null
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: custom/v1
|
|
||||||
kind: Custom
|
|
||||||
metadata:
|
|
||||||
name: cus
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
other: something
|
|
||||||
replicas: 42
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
Replica: types.Replica{
|
|
||||||
Name: "cus",
|
|
||||||
Count: 42,
|
|
||||||
},
|
|
||||||
FieldSpec: types.FieldSpec{
|
|
||||||
Path: "spec/template/replicas",
|
|
||||||
CreateIfNotPresent: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"no update if CreateIfNotPresent is false": {
|
|
||||||
input: `
|
|
||||||
apiVersion: custom/v1
|
|
||||||
kind: Custom
|
|
||||||
metadata:
|
|
||||||
name: cus
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
other: something
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: custom/v1
|
|
||||||
kind: Custom
|
|
||||||
metadata:
|
|
||||||
name: cus
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
other: something
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
Replica: types.Replica{
|
|
||||||
Name: "cus",
|
|
||||||
Count: 42,
|
|
||||||
},
|
|
||||||
FieldSpec: types.FieldSpec{
|
|
||||||
Path: "spec/template/replicas",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"update multiple fields": {
|
|
||||||
input: `
|
|
||||||
apiVersion: custom/v1
|
|
||||||
kind: Custom
|
|
||||||
metadata:
|
|
||||||
name: cus
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
replicas: 5
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: custom/v1
|
|
||||||
kind: Custom
|
|
||||||
metadata:
|
|
||||||
name: cus
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
replicas: 42
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
Replica: types.Replica{
|
|
||||||
Name: "cus",
|
|
||||||
Count: 42,
|
|
||||||
},
|
|
||||||
FieldSpec: types.FieldSpec{Path: "spec/template/replicas"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package valueadd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/filesys"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// An 'Add' operation aspiring to IETF RFC 6902 JSON.
|
|
||||||
//
|
|
||||||
// The filter tries to add a value to a node at a particular field path.
|
|
||||||
//
|
|
||||||
// Kinds of target fields:
|
|
||||||
//
|
|
||||||
// - Non-existent target field.
|
|
||||||
//
|
|
||||||
// The field will be added and the value inserted.
|
|
||||||
//
|
|
||||||
// - Existing field, scalar or map.
|
|
||||||
//
|
|
||||||
// E.g. 'spec/template/spec/containers/[name:nginx]/image'
|
|
||||||
//
|
|
||||||
// This behaves like an IETF RFC 6902 Replace operation would;
|
|
||||||
// the existing value is replaced without complaint, even though
|
|
||||||
// this is an Add operation. In contrast, a Replace operation
|
|
||||||
// must fail (report an error) if the field doesn't exist.
|
|
||||||
//
|
|
||||||
// - Existing field, list (array)
|
|
||||||
// Not supported yet.
|
|
||||||
// TODO: Honor fields with RFC-6902-style array indices
|
|
||||||
// TODO: like 'spec/template/spec/containers/2'
|
|
||||||
// TODO: Modify kyaml/yaml/PathGetter to allow this.
|
|
||||||
// The value will be inserted into the array at the given position,
|
|
||||||
// shifting other contents. To instead replace an array entry, use
|
|
||||||
// an implementation of an IETF RFC 6902 Replace operation.
|
|
||||||
//
|
|
||||||
// For the common case of a filepath in the field value, and a desire
|
|
||||||
// to add the value to the filepath (rather than replace the filepath),
|
|
||||||
// use a non-zero value of FilePathPosition (see below).
|
|
||||||
type Filter struct {
|
|
||||||
// Value is the value to add.
|
|
||||||
//
|
|
||||||
// Empty values are disallowed, i.e. this filter isn't intended
|
|
||||||
// for use in erasing or removing fields. For that, use a filter
|
|
||||||
// more aligned with the IETF RFC 6902 JSON Remove operation.
|
|
||||||
//
|
|
||||||
// At the time of writing, Value's value should be a simple string,
|
|
||||||
// not a JSON document. This particular filter focuses on easing
|
|
||||||
// injection of a single-sourced cloud project and/or cluster name
|
|
||||||
// into various fields, especially namespace and various filepath
|
|
||||||
// specifications.
|
|
||||||
Value string
|
|
||||||
|
|
||||||
// FieldPath is a JSON-style path to the field intended to hold the value.
|
|
||||||
FieldPath string
|
|
||||||
|
|
||||||
// FilePathPosition is a filepath field index.
|
|
||||||
//
|
|
||||||
// Call the value of this field _i_.
|
|
||||||
//
|
|
||||||
// If _i_ is zero, negative or unspecified, this field has no effect.
|
|
||||||
//
|
|
||||||
// If _i_ is > 0, then it's assumed that
|
|
||||||
// - 'Value' is a string that can work as a directory or file name,
|
|
||||||
// - the field value intended for replacement holds a filepath.
|
|
||||||
//
|
|
||||||
// The filepath is split into a string slice, the value is inserted
|
|
||||||
// at position [i-1], shifting the rest of the path to the right.
|
|
||||||
// A value of i==1 puts the new value at the start of the path.
|
|
||||||
// This change never converts an absolute path to a relative path,
|
|
||||||
// meaning adding a new field at position i==1 will preserve a
|
|
||||||
// leading slash. E.g. if Value == 'PEACH'
|
|
||||||
//
|
|
||||||
// OLD : NEW : FilePathPosition
|
|
||||||
// --------------------------------------------------------
|
|
||||||
// {empty} : PEACH : irrelevant
|
|
||||||
// / : /PEACH : irrelevant
|
|
||||||
// pie : PEACH/pie : 1 (or less to prefix)
|
|
||||||
// /pie : /PEACH/pie : 1 (or less to prefix)
|
|
||||||
// raw : raw/PEACH : 2 (or more to postfix)
|
|
||||||
// /raw : /raw/PEACH : 2 (or more to postfix)
|
|
||||||
// a/nice/warm/pie : a/nice/warm/PEACH/pie : 4
|
|
||||||
// /a/nice/warm/pie : /a/nice/warm/PEACH/pie : 4
|
|
||||||
//
|
|
||||||
// For robustness (liberal input, conservative output) FilePathPosition
|
|
||||||
// values that that are too large to index the split filepath result in a
|
|
||||||
// postfix rather than an error. So use 1 to prefix, 9999 to postfix.
|
|
||||||
FilePathPosition int `json:"filePathPosition,omitempty" yaml:"filePathPosition,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ kio.Filter = Filter{}
|
|
||||||
|
|
||||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|
||||||
_, err := kio.FilterAll(yaml.FilterFunc(
|
|
||||||
func(node *yaml.RNode) (*yaml.RNode, error) {
|
|
||||||
var fields []string
|
|
||||||
// if there is forward slash '/' in the field name, a back slash '\'
|
|
||||||
// will be used to escape it.
|
|
||||||
for _, f := range strings.Split(f.FieldPath, "/") {
|
|
||||||
if len(fields) > 0 && strings.HasSuffix(fields[len(fields)-1], "\\") {
|
|
||||||
concatField := strings.TrimSuffix(fields[len(fields)-1], "\\") + "/" + f
|
|
||||||
fields = append(fields[:len(fields)-1], concatField)
|
|
||||||
} else {
|
|
||||||
fields = append(fields, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: support SequenceNode.
|
|
||||||
// Presumably here one could look for array indices (digits) at
|
|
||||||
// the end of the field path (as described in IETF RFC 6902 JSON),
|
|
||||||
// and if found, take it as a signal that this should be a
|
|
||||||
// SequenceNode instead of a ScalarNode, and insert the value
|
|
||||||
// into the proper slot, shifting every over.
|
|
||||||
n, err := node.Pipe(yaml.LookupCreate(yaml.ScalarNode, fields...))
|
|
||||||
if err != nil {
|
|
||||||
return node, err
|
|
||||||
}
|
|
||||||
// TODO: allow more kinds
|
|
||||||
if err := yaml.ErrorIfInvalid(n, yaml.ScalarNode); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
newValue := f.Value
|
|
||||||
if f.FilePathPosition > 0 {
|
|
||||||
newValue = filesys.InsertPathPart(
|
|
||||||
n.YNode().Value, f.FilePathPosition-1, newValue)
|
|
||||||
}
|
|
||||||
return n.Pipe(yaml.FieldSetter{StringValue: newValue})
|
|
||||||
})).Filter(nodes)
|
|
||||||
return nodes, err
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package valueadd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
|
||||||
)
|
|
||||||
|
|
||||||
const someResource = `
|
|
||||||
kind: SomeKind
|
|
||||||
spec:
|
|
||||||
resourceRef:
|
|
||||||
external: projects/whatever
|
|
||||||
`
|
|
||||||
|
|
||||||
func TestValueAddFilter(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input string
|
|
||||||
expectedOutput string
|
|
||||||
filter Filter
|
|
||||||
}{
|
|
||||||
"simpleAdd": {
|
|
||||||
input: `
|
|
||||||
kind: SomeKind
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
kind: SomeKind
|
|
||||||
spec:
|
|
||||||
resourceRef:
|
|
||||||
external: valueAdded
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
Value: "valueAdded",
|
|
||||||
FieldPath: "spec/resourceRef/external",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"replaceExisting": {
|
|
||||||
input: someResource,
|
|
||||||
expectedOutput: `
|
|
||||||
kind: SomeKind
|
|
||||||
spec:
|
|
||||||
resourceRef:
|
|
||||||
external: valueAdded
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
Value: "valueAdded",
|
|
||||||
FieldPath: "spec/resourceRef/external",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"prefixExisting": {
|
|
||||||
input: someResource,
|
|
||||||
expectedOutput: `
|
|
||||||
kind: SomeKind
|
|
||||||
spec:
|
|
||||||
resourceRef:
|
|
||||||
external: valueAdded/projects/whatever
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
Value: "valueAdded",
|
|
||||||
FieldPath: "spec/resourceRef/external",
|
|
||||||
FilePathPosition: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"postfixExisting": {
|
|
||||||
input: someResource,
|
|
||||||
expectedOutput: `
|
|
||||||
kind: SomeKind
|
|
||||||
spec:
|
|
||||||
resourceRef:
|
|
||||||
external: projects/whatever/valueAdded
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
Value: "valueAdded",
|
|
||||||
FieldPath: "spec/resourceRef/external",
|
|
||||||
FilePathPosition: 99,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"placeInMiddleOfExisting": {
|
|
||||||
input: someResource,
|
|
||||||
expectedOutput: `
|
|
||||||
kind: SomeKind
|
|
||||||
spec:
|
|
||||||
resourceRef:
|
|
||||||
external: projects/valueAdded/whatever
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
Value: "valueAdded",
|
|
||||||
FieldPath: "spec/resourceRef/external",
|
|
||||||
FilePathPosition: 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"backSlash": {
|
|
||||||
input: `
|
|
||||||
kind: SomeKind
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
kind: SomeKind
|
|
||||||
spec:
|
|
||||||
resourceRef/external: valueAdded
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
Value: "valueAdded",
|
|
||||||
FieldPath: "spec/resourceRef\\/external",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
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_test.RunFilter(t, tc.input, filter))) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
16
api/go.mod
16
api/go.mod
@@ -1,16 +0,0 @@
|
|||||||
module sigs.k8s.io/kustomize/api
|
|
||||||
|
|
||||||
go 1.16
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/evanphx/json-patch v4.11.0+incompatible
|
|
||||||
github.com/go-errors/errors v1.0.1
|
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
|
||||||
github.com/imdario/mergo v0.3.5
|
|
||||||
github.com/pkg/errors v0.9.1
|
|
||||||
github.com/stretchr/testify v1.5.1
|
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
|
||||||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e
|
|
||||||
sigs.k8s.io/kustomize/kyaml v0.11.0
|
|
||||||
sigs.k8s.io/yaml v1.2.0
|
|
||||||
)
|
|
||||||
230
api/go.sum
230
api/go.sum
@@ -1,230 +0,0 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
|
||||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
|
||||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
|
||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
|
||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
|
||||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
|
||||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
|
||||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
|
||||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
|
||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
|
||||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
|
||||||
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
|
|
||||||
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
|
||||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
|
||||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
|
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
|
||||||
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
|
|
||||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
|
||||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
|
||||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
|
||||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
|
||||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
|
||||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
|
||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
|
||||||
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
|
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
|
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
|
||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
|
||||||
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
|
|
||||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
|
||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
|
||||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
|
||||||
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
|
||||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
|
||||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
|
|
||||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
|
||||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
|
||||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
|
||||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
|
||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
|
||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
|
||||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
|
||||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
|
||||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
|
||||||
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
|
||||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
|
||||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca h1:1CFlNzQhALwjS9mBAUkycX616GzgsuYUOCHA5+HSlXI=
|
|
||||||
github.com/xlab/treeprint v0.0.0-20181112141820-a009c3971eca/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg=
|
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
|
||||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc=
|
|
||||||
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o=
|
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
|
||||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
|
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
|
||||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
|
||||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
|
||||||
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
|
||||||
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
|
|
||||||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
|
|
||||||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
|
|
||||||
sigs.k8s.io/kustomize/kyaml v0.11.0 h1:9KhiCPKaVyuPcgOLJXkvytOvjMJLoxpjodiycb4gHsA=
|
|
||||||
sigs.k8s.io/kustomize/kyaml v0.11.0/go.mod h1:GNMwjim4Ypgp/MueD3zXHLRJEjz7RvtPae0AwlvEMFM=
|
|
||||||
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
|
|
||||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
|
||||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package hasher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SortArrayAndComputeHash sorts a string array and
|
|
||||||
// returns a hash for it
|
|
||||||
func SortArrayAndComputeHash(s []string) (string, error) {
|
|
||||||
sort.Strings(s)
|
|
||||||
data, err := json.Marshal(s)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return encode(hex256(string(data)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from https://github.com/kubernetes/kubernetes
|
|
||||||
// /blob/master/pkg/kubectl/util/hash/hash.go
|
|
||||||
func encode(hex string) (string, error) {
|
|
||||||
if len(hex) < 10 {
|
|
||||||
return "", fmt.Errorf(
|
|
||||||
"input length must be at least 10")
|
|
||||||
}
|
|
||||||
enc := []rune(hex[:10])
|
|
||||||
for i := range enc {
|
|
||||||
switch enc[i] {
|
|
||||||
case '0':
|
|
||||||
enc[i] = 'g'
|
|
||||||
case '1':
|
|
||||||
enc[i] = 'h'
|
|
||||||
case '3':
|
|
||||||
enc[i] = 'k'
|
|
||||||
case 'a':
|
|
||||||
enc[i] = 'm'
|
|
||||||
case 'e':
|
|
||||||
enc[i] = 't'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(enc), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// hex256 returns the hex form of the sha256 of the argument.
|
|
||||||
func hex256(data string) string {
|
|
||||||
return fmt.Sprintf("%x", sha256.Sum256([]byte(data)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hasher computes the hash of an RNode.
|
|
||||||
type Hasher struct{}
|
|
||||||
|
|
||||||
// Hash returns a hash of the argument.
|
|
||||||
func (h *Hasher) Hash(node *yaml.RNode) (r string, err error) {
|
|
||||||
var encoded string
|
|
||||||
switch node.GetKind() {
|
|
||||||
case "ConfigMap":
|
|
||||||
encoded, err = encodeConfigMap(node)
|
|
||||||
case "Secret":
|
|
||||||
encoded, err = encodeSecret(node)
|
|
||||||
default:
|
|
||||||
var encodedBytes []byte
|
|
||||||
encodedBytes, err = json.Marshal(node.YNode())
|
|
||||||
encoded = string(encodedBytes)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return encode(hex256(encoded))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNodeValues(
|
|
||||||
node *yaml.RNode, paths []string) (map[string]interface{}, error) {
|
|
||||||
values := make(map[string]interface{})
|
|
||||||
for _, p := range paths {
|
|
||||||
vn, err := node.Pipe(yaml.Lookup(p))
|
|
||||||
if err != nil {
|
|
||||||
return map[string]interface{}{}, err
|
|
||||||
}
|
|
||||||
if vn == nil {
|
|
||||||
values[p] = ""
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if vn.YNode().Kind != yaml.ScalarNode {
|
|
||||||
vs, err := vn.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
return map[string]interface{}{}, err
|
|
||||||
}
|
|
||||||
// data, binaryData and stringData are all maps
|
|
||||||
var v map[string]interface{}
|
|
||||||
json.Unmarshal(vs, &v)
|
|
||||||
values[p] = v
|
|
||||||
} else {
|
|
||||||
values[p] = vn.YNode().Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return values, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeConfigMap encodes a ConfigMap.
|
|
||||||
// Data, Kind, and Name are taken into account.
|
|
||||||
// BinaryData is included if it's not empty to avoid useless key in output.
|
|
||||||
func encodeConfigMap(node *yaml.RNode) (string, error) {
|
|
||||||
// get fields
|
|
||||||
paths := []string{"metadata/name", "data", "binaryData"}
|
|
||||||
values, err := getNodeValues(node, paths)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
m := map[string]interface{}{
|
|
||||||
"kind": "ConfigMap",
|
|
||||||
"name": values["metadata/name"],
|
|
||||||
"data": values["data"],
|
|
||||||
}
|
|
||||||
if _, ok := values["binaryData"].(map[string]interface{}); ok {
|
|
||||||
m["binaryData"] = values["binaryData"]
|
|
||||||
}
|
|
||||||
|
|
||||||
// json.Marshal sorts the keys in a stable order in the encoding
|
|
||||||
data, err := json.Marshal(m)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(data), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodeSecret encodes a Secret.
|
|
||||||
// Data, Kind, Name, and Type are taken into account.
|
|
||||||
// StringData is included if it's not empty to avoid useless key in output.
|
|
||||||
func encodeSecret(node *yaml.RNode) (string, error) {
|
|
||||||
// get fields
|
|
||||||
paths := []string{"type", "metadata/name", "data", "stringData"}
|
|
||||||
values, err := getNodeValues(node, paths)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
m := map[string]interface{}{"kind": "Secret", "type": values["type"],
|
|
||||||
"name": values["metadata/name"], "data": values["data"]}
|
|
||||||
if _, ok := values["stringData"].(map[string]interface{}); ok {
|
|
||||||
m["stringData"] = values["stringData"]
|
|
||||||
}
|
|
||||||
|
|
||||||
// json.Marshal sorts the keys in a stable order in the encoding
|
|
||||||
data, err := json.Marshal(m)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(data), nil
|
|
||||||
}
|
|
||||||
@@ -1,356 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package hasher
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSortArrayAndComputeHash(t *testing.T) {
|
|
||||||
array1 := []string{"a", "b", "c", "d"}
|
|
||||||
array2 := []string{"c", "b", "d", "a"}
|
|
||||||
h1, err := SortArrayAndComputeHash(array1)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
if h1 == "" {
|
|
||||||
t.Errorf("failed to hash %v", array1)
|
|
||||||
}
|
|
||||||
h2, err := SortArrayAndComputeHash(array2)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
if h2 == "" {
|
|
||||||
t.Errorf("failed to hash %v", array2)
|
|
||||||
}
|
|
||||||
if h1 != h2 {
|
|
||||||
t.Errorf("hash is not consistent with reordered list: %s %s", h1, h2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_hex256(t *testing.T) {
|
|
||||||
// hash the empty string to be sure that sha256 is being used
|
|
||||||
expect := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
|
||||||
sum := hex256("")
|
|
||||||
if expect != sum {
|
|
||||||
t.Errorf("expected hash %q but got %q", expect, sum)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestConfigMapHash(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
cmYaml string
|
|
||||||
hash string
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
// empty map
|
|
||||||
{"empty data", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap`, "6ct58987ht", ""},
|
|
||||||
// one key
|
|
||||||
{"one key", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
data:
|
|
||||||
one: ""`, "9g67k2htb6", ""},
|
|
||||||
// three keys (tests sorting order)
|
|
||||||
{"three keys", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
data:
|
|
||||||
two: 2
|
|
||||||
one: ""
|
|
||||||
three: 3`, "7757f9kkct", ""},
|
|
||||||
// empty binary data map
|
|
||||||
{"empty binary data", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap`, "6ct58987ht", ""},
|
|
||||||
// one key with binary data
|
|
||||||
{"one key with binary data", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
binaryData:
|
|
||||||
one: ""`, "6mtk2m274t", ""},
|
|
||||||
// three keys with binary data (tests sorting order)
|
|
||||||
{"three keys with binary data", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
binaryData:
|
|
||||||
two: 2
|
|
||||||
one: ""
|
|
||||||
three: 3`, "9th7kc28dg", ""},
|
|
||||||
// two keys, one with string and another with binary data
|
|
||||||
{"two keys with one each", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
data:
|
|
||||||
one: ""
|
|
||||||
binaryData:
|
|
||||||
two: ""`, "698h7c7t9m", ""},
|
|
||||||
}
|
|
||||||
h := &Hasher{}
|
|
||||||
for _, c := range cases {
|
|
||||||
node, err := yaml.Parse(c.cmYaml)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
hashed, err := h.Hash(node)
|
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c.hash != hashed {
|
|
||||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSecretHash(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
secretYaml string
|
|
||||||
hash string
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
// empty map
|
|
||||||
{"empty data", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
type: my-type`, "5gmgkf8578", ""},
|
|
||||||
// one key
|
|
||||||
{"one key", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
type: my-type
|
|
||||||
data:
|
|
||||||
one: ""`, "74bd68bm66", ""},
|
|
||||||
// three keys (tests sorting order)
|
|
||||||
{"three keys", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
type: my-type
|
|
||||||
data:
|
|
||||||
two: 2
|
|
||||||
one: ""
|
|
||||||
three: 3`, "4gf75c7476", ""},
|
|
||||||
// with stringdata
|
|
||||||
{"stringdata", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
type: my-type
|
|
||||||
data:
|
|
||||||
one: ""
|
|
||||||
stringData:
|
|
||||||
two: 2`, "c4h4264gdb", ""},
|
|
||||||
// empty stringdata
|
|
||||||
{"empty stringdata", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
type: my-type
|
|
||||||
data:
|
|
||||||
one: ""`, "74bd68bm66", ""},
|
|
||||||
}
|
|
||||||
h := &Hasher{}
|
|
||||||
for _, c := range cases {
|
|
||||||
node, err := yaml.Parse(c.secretYaml)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
hashed, err := h.Hash(node)
|
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if c.hash != hashed {
|
|
||||||
t.Errorf("case %q, expect hash %q but got %q", c.desc, c.hash, h)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBasicHash(t *testing.T) {
|
|
||||||
cases := map[string]struct {
|
|
||||||
res string
|
|
||||||
hash string
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
"minimal": {`
|
|
||||||
apiVersion: test/v1
|
|
||||||
kind: TestResource
|
|
||||||
metadata:
|
|
||||||
name: my-resource`, "244782mkb7", ""},
|
|
||||||
"with spec": {`
|
|
||||||
apiVersion: test/v1
|
|
||||||
kind: TestResource
|
|
||||||
metadata:
|
|
||||||
name: my-resource
|
|
||||||
spec:
|
|
||||||
foo: 1
|
|
||||||
bar: abc`, "59m2mdccg4", ""},
|
|
||||||
}
|
|
||||||
h := &Hasher{}
|
|
||||||
for n := range cases {
|
|
||||||
c := cases[n]
|
|
||||||
t.Run(n, func(t *testing.T) {
|
|
||||||
node, err := yaml.Parse(c.res)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
hashed, err := h.Hash(node)
|
|
||||||
if SkipRest(t, n, err, c.err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if c.hash != hashed {
|
|
||||||
t.Errorf("case %q, expect hash %q but got %q", n, c.hash, h)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeConfigMap(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
cmYaml string
|
|
||||||
expect string
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
// empty map
|
|
||||||
{"empty data", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""},
|
|
||||||
// one key
|
|
||||||
{"one key", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
data:
|
|
||||||
one: ""`, `{"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
|
||||||
// three keys (tests sorting order)
|
|
||||||
{"three keys", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
data:
|
|
||||||
two: 2
|
|
||||||
one: ""
|
|
||||||
three: 3`, `{"data":{"one":"","three":3,"two":2},"kind":"ConfigMap","name":""}`, ""},
|
|
||||||
// empty binary map
|
|
||||||
{"empty data", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap`, `{"data":"","kind":"ConfigMap","name":""}`, ""},
|
|
||||||
// one key with binary data
|
|
||||||
{"one key", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
binaryData:
|
|
||||||
one: ""`, `{"binaryData":{"one":""},"data":"","kind":"ConfigMap","name":""}`, ""},
|
|
||||||
// three keys with binary data (tests sorting order)
|
|
||||||
{"three keys", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
binaryData:
|
|
||||||
two: 2
|
|
||||||
one: ""
|
|
||||||
three: 3`, `{"binaryData":{"one":"","three":3,"two":2},"data":"","kind":"ConfigMap","name":""}`, ""},
|
|
||||||
// two keys, one string and one binary values
|
|
||||||
{"two keys with one each", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ConfigMap
|
|
||||||
data:
|
|
||||||
one: ""
|
|
||||||
binaryData:
|
|
||||||
two: ""`, `{"binaryData":{"two":""},"data":{"one":""},"kind":"ConfigMap","name":""}`, ""},
|
|
||||||
}
|
|
||||||
for _, c := range cases {
|
|
||||||
node, err := yaml.Parse(c.cmYaml)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
s, err := encodeConfigMap(node)
|
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if s != c.expect {
|
|
||||||
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.cmYaml)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeSecret(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
desc string
|
|
||||||
secretYaml string
|
|
||||||
expect string
|
|
||||||
err string
|
|
||||||
}{
|
|
||||||
// empty map
|
|
||||||
{"empty data", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
type: my-type`, `{"data":"","kind":"Secret","name":"","type":"my-type"}`, ""},
|
|
||||||
// one key
|
|
||||||
{"one key", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
type: my-type
|
|
||||||
data:
|
|
||||||
one: ""`, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
|
||||||
// three keys (tests sorting order) - note json.Marshal base64 encodes the values because they come in as []byte
|
|
||||||
{"three keys", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
type: my-type
|
|
||||||
data:
|
|
||||||
two: 2
|
|
||||||
one: ""
|
|
||||||
three: 3`, `{"data":{"one":"","three":3,"two":2},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
|
||||||
// with stringdata
|
|
||||||
{"stringdata", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
type: my-type
|
|
||||||
data:
|
|
||||||
one: ""
|
|
||||||
stringData:
|
|
||||||
two: 2`, `{"data":{"one":""},"kind":"Secret","name":"","stringData":{"two":2},"type":"my-type"}`, ""},
|
|
||||||
// empty stringdata
|
|
||||||
{"empty stringdata", `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
type: my-type
|
|
||||||
data:
|
|
||||||
one: ""`, `{"data":{"one":""},"kind":"Secret","name":"","type":"my-type"}`, ""},
|
|
||||||
}
|
|
||||||
for _, c := range cases {
|
|
||||||
node, err := yaml.Parse(c.secretYaml)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
s, err := encodeSecret(node)
|
|
||||||
if SkipRest(t, c.desc, err, c.err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if s != c.expect {
|
|
||||||
t.Errorf("case %q, expect %q but got %q from encode %#v", c.desc, c.expect, s, c.secretYaml)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SkipRest returns true if there was a non-nil error or if we expected an
|
|
||||||
// error that didn't happen, and logs the appropriate error on the test object.
|
|
||||||
// The return value indicates whether we should skip the rest of the test case
|
|
||||||
// due to the error result.
|
|
||||||
func SkipRest(t *testing.T, desc string, err error, contains string) bool {
|
|
||||||
if err != nil {
|
|
||||||
if len(contains) == 0 {
|
|
||||||
t.Errorf("case %q, expect nil error but got %q", desc, err.Error())
|
|
||||||
} else if !strings.Contains(err.Error(), contains) {
|
|
||||||
t.Errorf("case %q, expect error to contain %q but got %q", desc, contains, err.Error())
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else if len(contains) > 0 {
|
|
||||||
t.Errorf("case %q, expect error to contain %q but got nil error", desc, contains)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user