mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-29 09:40:49 +00:00
Compare commits
1 Commits
monopole-p
...
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 -->
|
|
||||||
100
.github/workflows/go.yml
vendored
100
.github/workflows/go.yml
vendored
@@ -1,100 +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 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 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
|
|
||||||
|
|
||||||
- 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,14 +1,11 @@
|
|||||||
aliases:
|
aliases:
|
||||||
kustomize-admins: # Please keep in sync with kustomize-admins in https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml
|
kustomize-admins:
|
||||||
- knverey
|
|
||||||
- monopole
|
- monopole
|
||||||
- pwittrock
|
- pwittrock
|
||||||
kustomize-maintainers: # Please keep in sync with kustomize-maintainers in https://github.com/kubernetes/org/blob/main/config/kubernetes-sigs/sig-cli/teams.yaml
|
kustomize-maintainers:
|
||||||
|
- droot
|
||||||
- justinsb
|
- justinsb
|
||||||
- mortent
|
|
||||||
- natasha41575
|
|
||||||
- phanimarupaka
|
|
||||||
- Shell32-Natsu
|
|
||||||
emeritus-maintainers:
|
|
||||||
- liujingfang1
|
- liujingfang1
|
||||||
- mengqiy
|
- mengqiy
|
||||||
|
- monopole
|
||||||
|
- pwittrock
|
||||||
|
|||||||
84
README.md
84
README.md
@@ -9,31 +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 has remained frozen at v2.0.3 while work
|
|
||||||
to extract kubectl from the k/k repo, and work to remove
|
|
||||||
kustomize's dependence on core k/k code ([#2506]) has proceeded.
|
|
||||||
The reintegration effort is tracked in [#1500] (and its blocking
|
|
||||||
issues).
|
|
||||||
|
|
||||||
[v2.0.3]: /../../tree/v2.0.3
|
| kubectl version | kustomize version |
|
||||||
[#2506]: https://github.com/kubernetes-sigs/kustomize/issues/2506
|
|---------|--------|
|
||||||
[#1500]: https://github.com/kubernetes-sigs/kustomize/issues/1500
|
| v1.16.0 | [v2.0.3](https://github.com/kubernetes-sigs/kustomize/tree/v2.0.3) |
|
||||||
|
| v1.15.x | [v2.0.3](https://github.com/kubernetes-sigs/kustomize/tree/v2.0.3) |
|
||||||
|
| v1.14.x | [v2.0.3](https://github.com/kubernetes-sigs/kustomize/tree/v2.0.3) |
|
||||||
|
|
||||||
For examples and guides for using the kubectl integration please
|
For examples and guides for using the kubectl integration please see the [kubectl book] or the [kubernetes documentation].
|
||||||
see the [kubectl book] or the [kubernetes documentation].
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -108,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.
|
||||||
|
|
||||||
@@ -134,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
|
||||||
|
|
||||||
@@ -144,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,7 +0,0 @@
|
|||||||
// Copyright 2019 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.
|
|
||||||
package filesys
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package filesys
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// File groups the basic os.File methods.
|
|
||||||
type File interface {
|
|
||||||
io.ReadWriteCloser
|
|
||||||
Stat() (os.FileInfo, error)
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package filesys
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ os.FileInfo = fileInfo{}
|
|
||||||
|
|
||||||
// fileInfo implements os.FileInfo for a fileInMemory instance.
|
|
||||||
type fileInfo struct {
|
|
||||||
node *fsNode
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the file
|
|
||||||
func (fi fileInfo) Name() string { return fi.node.Name() }
|
|
||||||
|
|
||||||
// Size returns the size of the file
|
|
||||||
func (fi fileInfo) Size() int64 { return fi.node.Size() }
|
|
||||||
|
|
||||||
// Mode returns the file mode
|
|
||||||
func (fi fileInfo) Mode() os.FileMode { return 0777 }
|
|
||||||
|
|
||||||
// ModTime returns a bogus time
|
|
||||||
func (fi fileInfo) ModTime() time.Time { return time.Time{} }
|
|
||||||
|
|
||||||
// IsDir returns true if it is a directory
|
|
||||||
func (fi fileInfo) IsDir() bool { return fi.node.isNodeADir() }
|
|
||||||
|
|
||||||
// Sys should return underlying data source, but it now returns nil
|
|
||||||
func (fi fileInfo) Sys() interface{} { return nil }
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package filesys
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
Separator = string(filepath.Separator)
|
|
||||||
SelfDir = "."
|
|
||||||
ParentDir = ".."
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileSystem groups basic os filesystem methods.
|
|
||||||
// It's supposed be functional subset of https://golang.org/pkg/os
|
|
||||||
type FileSystem interface {
|
|
||||||
// Create a file.
|
|
||||||
Create(path string) (File, error)
|
|
||||||
// MkDir makes a directory.
|
|
||||||
Mkdir(path string) error
|
|
||||||
// MkDirAll makes a directory path, creating intervening directories.
|
|
||||||
MkdirAll(path string) error
|
|
||||||
// RemoveAll removes path and any children it contains.
|
|
||||||
RemoveAll(path string) error
|
|
||||||
// Open opens the named file for reading.
|
|
||||||
Open(path string) (File, error)
|
|
||||||
// IsDir returns true if the path is a directory.
|
|
||||||
IsDir(path string) bool
|
|
||||||
// ReadDir returns a list of files and directories within a directory.
|
|
||||||
ReadDir(path string) ([]string, error)
|
|
||||||
// CleanedAbs converts the given path into a
|
|
||||||
// directory and a file name, where the directory
|
|
||||||
// is represented as a ConfirmedDir and all that implies.
|
|
||||||
// If the entire path is a directory, the file component
|
|
||||||
// is an empty string.
|
|
||||||
CleanedAbs(path string) (ConfirmedDir, string, error)
|
|
||||||
// Exists is true if the path exists in the file system.
|
|
||||||
Exists(path string) bool
|
|
||||||
// Glob returns the list of matching files,
|
|
||||||
// emulating https://golang.org/pkg/path/filepath/#Glob
|
|
||||||
Glob(pattern string) ([]string, error)
|
|
||||||
// ReadFile returns the contents of the file at the given path.
|
|
||||||
ReadFile(path string) ([]byte, error)
|
|
||||||
// WriteFile writes the data to a file at the given path,
|
|
||||||
// overwriting anything that's already there.
|
|
||||||
WriteFile(path string, data []byte) error
|
|
||||||
// Walk walks the file system with the given WalkFunc.
|
|
||||||
Walk(path string, walkFn filepath.WalkFunc) error
|
|
||||||
}
|
|
||||||
@@ -1,631 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package filesys
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ File = &fsNode{}
|
|
||||||
var _ FileSystem = &fsNode{}
|
|
||||||
|
|
||||||
// fsNode is either a file or a directory.
|
|
||||||
type fsNode struct {
|
|
||||||
// What node owns me?
|
|
||||||
parent *fsNode
|
|
||||||
|
|
||||||
// Value to return as the Name() when the
|
|
||||||
// parent is nil.
|
|
||||||
nilParentName string
|
|
||||||
|
|
||||||
// A directory mapping names to nodes.
|
|
||||||
// If dir is nil, then self node is a file.
|
|
||||||
// If dir is non-nil, then self node is a directory,
|
|
||||||
// albeit possibly an empty directory.
|
|
||||||
dir map[string]*fsNode
|
|
||||||
|
|
||||||
// if this node is a file, this is the content.
|
|
||||||
content []byte
|
|
||||||
|
|
||||||
// if offset is not nil the file is open and it tracks
|
|
||||||
// the current file offset.
|
|
||||||
offset *int
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeEmptyDirInMemory returns an empty directory.
|
|
||||||
// The paths of nodes in this object will never
|
|
||||||
// report a leading Separator, meaning they
|
|
||||||
// aren't "absolute" in the sense defined by
|
|
||||||
// https://golang.org/pkg/path/filepath/#IsAbs.
|
|
||||||
func MakeEmptyDirInMemory() *fsNode {
|
|
||||||
return &fsNode{
|
|
||||||
dir: make(map[string]*fsNode),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeFsInMemory returns an empty 'file system'.
|
|
||||||
// The paths of nodes in this object will always
|
|
||||||
// report a leading Separator, meaning they
|
|
||||||
// are "absolute" in the sense defined by
|
|
||||||
// https://golang.org/pkg/path/filepath/#IsAbs.
|
|
||||||
// This is a relevant difference when using Walk,
|
|
||||||
// Glob, Match, etc.
|
|
||||||
func MakeFsInMemory() FileSystem {
|
|
||||||
return &fsNode{
|
|
||||||
nilParentName: Separator,
|
|
||||||
dir: make(map[string]*fsNode),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name of the node.
|
|
||||||
func (n *fsNode) Name() string {
|
|
||||||
if n.parent == nil {
|
|
||||||
// Unable to lookup name in parent.
|
|
||||||
return n.nilParentName
|
|
||||||
}
|
|
||||||
if !n.parent.isNodeADir() {
|
|
||||||
log.Fatal("parent not a dir")
|
|
||||||
}
|
|
||||||
for key, value := range n.parent.dir {
|
|
||||||
if value == n {
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Fatal("unable to find fsNode name")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path returns the full path to the node.
|
|
||||||
func (n *fsNode) Path() string {
|
|
||||||
if n.parent == nil {
|
|
||||||
return n.nilParentName
|
|
||||||
}
|
|
||||||
if !n.parent.isNodeADir() {
|
|
||||||
log.Fatal("parent not a dir, structural error")
|
|
||||||
}
|
|
||||||
return filepath.Join(n.parent.Path(), n.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
// mySplit trims trailing separators from the directory
|
|
||||||
// result of filepath.Split.
|
|
||||||
func mySplit(s string) (string, string) {
|
|
||||||
dName, fName := filepath.Split(s)
|
|
||||||
return StripTrailingSeps(dName), fName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *fsNode) addFile(name string, c []byte) (result *fsNode, err error) {
|
|
||||||
parent := n
|
|
||||||
dName, fileName := mySplit(name)
|
|
||||||
if dName != "" {
|
|
||||||
parent, err = parent.addDir(dName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isLegalFileNameForCreation(fileName) {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"illegal name '%s' in file creation", fileName)
|
|
||||||
}
|
|
||||||
result, ok := parent.dir[fileName]
|
|
||||||
if ok {
|
|
||||||
// File already exists; overwrite it.
|
|
||||||
if result.offset != nil {
|
|
||||||
return nil, fmt.Errorf("cannot add already opened file '%s'", n.Path())
|
|
||||||
}
|
|
||||||
result.content = c
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
result = &fsNode{
|
|
||||||
content: c,
|
|
||||||
parent: parent,
|
|
||||||
}
|
|
||||||
parent.dir[fileName] = result
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create implements FileSystem.
|
|
||||||
// Create makes an empty file.
|
|
||||||
func (n *fsNode) Create(path string) (result File, err error) {
|
|
||||||
f, err := n.AddFile(path, nil)
|
|
||||||
if err != nil {
|
|
||||||
return f, err
|
|
||||||
}
|
|
||||||
f.offset = new(int)
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteFile implements FileSystem.
|
|
||||||
func (n *fsNode) WriteFile(path string, d []byte) error {
|
|
||||||
_, err := n.AddFile(path, d)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddFile adds a file and any necessary containing
|
|
||||||
// directories to the node.
|
|
||||||
func (n *fsNode) AddFile(
|
|
||||||
name string, c []byte) (result *fsNode, err error) {
|
|
||||||
if n.dir == nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"cannot add a file to a non-directory '%s'", n.Name())
|
|
||||||
}
|
|
||||||
return n.addFile(cleanQueryPath(name), c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *fsNode) addDir(path string) (result *fsNode, err error) {
|
|
||||||
|
|
||||||
parent := n
|
|
||||||
dName, subDirName := mySplit(path)
|
|
||||||
if dName != "" {
|
|
||||||
parent, err = n.addDir(dName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch subDirName {
|
|
||||||
case "", SelfDir:
|
|
||||||
return n, nil
|
|
||||||
case ParentDir:
|
|
||||||
if n.parent == nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"cannot add a directory above '%s'", n.Path())
|
|
||||||
}
|
|
||||||
return n.parent, nil
|
|
||||||
default:
|
|
||||||
if !isLegalFileNameForCreation(subDirName) {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"illegal name '%s' in directory creation", subDirName)
|
|
||||||
}
|
|
||||||
result, ok := parent.dir[subDirName]
|
|
||||||
if ok {
|
|
||||||
if result.isNodeADir() {
|
|
||||||
// it's already there.
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"cannot make dir '%s'; a file of that name already exists in '%s'",
|
|
||||||
subDirName, parent.Name())
|
|
||||||
}
|
|
||||||
result = &fsNode{
|
|
||||||
dir: make(map[string]*fsNode),
|
|
||||||
parent: parent,
|
|
||||||
}
|
|
||||||
parent.dir[subDirName] = result
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mkdir implements FileSystem.
|
|
||||||
// Mkdir creates a directory.
|
|
||||||
func (n *fsNode) Mkdir(path string) error {
|
|
||||||
_, err := n.AddDir(path)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MkdirAll implements FileSystem.
|
|
||||||
// MkdirAll creates a directory.
|
|
||||||
func (n *fsNode) MkdirAll(path string) error {
|
|
||||||
_, err := n.AddDir(path)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddDir adds a directory to the node, not complaining
|
|
||||||
// if it is already there.
|
|
||||||
func (n *fsNode) AddDir(path string) (result *fsNode, err error) {
|
|
||||||
if n.dir == nil {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"cannot add a directory to file node '%s'", n.Name())
|
|
||||||
}
|
|
||||||
return n.addDir(cleanQueryPath(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanedAbs implements FileSystem.
|
|
||||||
func (n *fsNode) CleanedAbs(path string) (ConfirmedDir, string, error) {
|
|
||||||
node, err := n.Find(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", errors.Wrap(err, "unable to clean")
|
|
||||||
}
|
|
||||||
if node == nil {
|
|
||||||
return "", "", fmt.Errorf("'%s' doesn't exist", path)
|
|
||||||
}
|
|
||||||
if node.isNodeADir() {
|
|
||||||
return ConfirmedDir(node.Path()), "", nil
|
|
||||||
}
|
|
||||||
return ConfirmedDir(node.parent.Path()), node.Name(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists implements FileSystem.
|
|
||||||
// Exists returns true if the path exists.
|
|
||||||
func (n *fsNode) Exists(path string) bool {
|
|
||||||
if !n.isNodeADir() {
|
|
||||||
return n.Name() == path
|
|
||||||
}
|
|
||||||
result, err := n.Find(path)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return result != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cleanQueryPath(path string) string {
|
|
||||||
// Always ignore leading separator?
|
|
||||||
// Remember that filepath.Clean returns "." if
|
|
||||||
// given an empty string argument.
|
|
||||||
return filepath.Clean(StripLeadingSeps(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find finds the given node, else nil if not found.
|
|
||||||
// Return error on structural/argument errors.
|
|
||||||
func (n *fsNode) Find(path string) (*fsNode, error) {
|
|
||||||
if !n.isNodeADir() {
|
|
||||||
return nil, fmt.Errorf("can only find inside a dir")
|
|
||||||
}
|
|
||||||
if path == "" {
|
|
||||||
// Special case; check *before* cleaning and *before*
|
|
||||||
// comparison to nilParentName.
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if (n.parent == nil && path == n.nilParentName) || path == SelfDir {
|
|
||||||
// Special case
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
return n.findIt(cleanQueryPath(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *fsNode) findIt(path string) (result *fsNode, err error) {
|
|
||||||
parent := n
|
|
||||||
dName, item := mySplit(path)
|
|
||||||
if dName != "" {
|
|
||||||
parent, err = n.findIt(dName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if parent == nil {
|
|
||||||
// all done, target doesn't exist.
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !parent.isNodeADir() {
|
|
||||||
return nil, fmt.Errorf("'%s' is not a directory", parent.Path())
|
|
||||||
}
|
|
||||||
return parent.dir[item], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveAll implements FileSystem.
|
|
||||||
// RemoveAll removes an item and everything it contains.
|
|
||||||
func (n *fsNode) RemoveAll(path string) error {
|
|
||||||
result, err := n.Find(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
return fmt.Errorf("cannot find '%s' to remove it", path)
|
|
||||||
}
|
|
||||||
return result.Remove()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove drop the node, and everything it contains, from its parent.
|
|
||||||
func (n *fsNode) Remove() error {
|
|
||||||
if n.parent == nil {
|
|
||||||
return fmt.Errorf("cannot remove a root node")
|
|
||||||
}
|
|
||||||
if !n.parent.isNodeADir() {
|
|
||||||
log.Fatal("parent not a dir")
|
|
||||||
}
|
|
||||||
for key, value := range n.parent.dir {
|
|
||||||
if value == n {
|
|
||||||
delete(n.parent.dir, key)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Fatal("unable to find self in parent")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isNodeADir returns true if the node is a directory.
|
|
||||||
// Cannot collide with the poorly named "IsDir".
|
|
||||||
func (n *fsNode) isNodeADir() bool {
|
|
||||||
return n.dir != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDir implements FileSystem.
|
|
||||||
// IsDir returns true if the argument resolves
|
|
||||||
// to a directory rooted at the node.
|
|
||||||
func (n *fsNode) IsDir(path string) bool {
|
|
||||||
result, err := n.Find(path)
|
|
||||||
if err != nil || result == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return result.isNodeADir()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadDir implements FileSystem.
|
|
||||||
func (n *fsNode) ReadDir(path string) ([]string, error) {
|
|
||||||
if !n.IsDir(path) {
|
|
||||||
return nil, fmt.Errorf("%s is not a directory", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
dir, err := n.Find(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if dir == nil {
|
|
||||||
return nil, fmt.Errorf("could not find directory %s", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := make([]string, len(dir.dir))
|
|
||||||
i := 0
|
|
||||||
for k := range dir.dir {
|
|
||||||
keys[i] = k
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return keys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the size of the node.
|
|
||||||
func (n *fsNode) Size() int64 {
|
|
||||||
if n.isNodeADir() {
|
|
||||||
return int64(len(n.dir))
|
|
||||||
}
|
|
||||||
return int64(len(n.content))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open implements FileSystem.
|
|
||||||
// Open opens the node in read-write mode and sets the offset its start.
|
|
||||||
// Writing right after opening the file will replace the original content
|
|
||||||
// and move the offset forward, as with a file opened with O_RDWR | O_CREATE.
|
|
||||||
//
|
|
||||||
// As an example, let's consider a file with content "content":
|
|
||||||
// - open: sets offset to start, content is "content"
|
|
||||||
// - write "@": offset increases by one, the content is now "@ontent"
|
|
||||||
// - read the rest: since offset is 1, the read operation returns "ontent"
|
|
||||||
// - write "$": offset is at EOF, so "$" is appended and content is now "@ontent$"
|
|
||||||
// - read the rest: returns 0 bytes and EOF
|
|
||||||
// - close: the content is still "@ontent$"
|
|
||||||
func (n *fsNode) Open(path string) (File, error) {
|
|
||||||
result, err := n.Find(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
return nil, fmt.Errorf("cannot find '%s' to open it", path)
|
|
||||||
}
|
|
||||||
if result.offset != nil {
|
|
||||||
return nil, fmt.Errorf("cannot open previously opened file '%s'", path)
|
|
||||||
}
|
|
||||||
result.offset = new(int)
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close marks the node closed.
|
|
||||||
func (n *fsNode) Close() error {
|
|
||||||
if n.offset == nil {
|
|
||||||
return fmt.Errorf("cannot close already closed file '%s'", n.Path())
|
|
||||||
}
|
|
||||||
n.offset = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadFile implements FileSystem.
|
|
||||||
func (n *fsNode) ReadFile(path string) (c []byte, err error) {
|
|
||||||
result, err := n.Find(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
return nil, fmt.Errorf("cannot find '%s' to read it", path)
|
|
||||||
}
|
|
||||||
if result.isNodeADir() {
|
|
||||||
return nil, fmt.Errorf("cannot read content from non-file '%s'", n.Path())
|
|
||||||
}
|
|
||||||
c = make([]byte, len(result.content))
|
|
||||||
copy(c, result.content)
|
|
||||||
return c, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read returns the content of the file node.
|
|
||||||
func (n *fsNode) Read(d []byte) (c int, err error) {
|
|
||||||
if n.isNodeADir() {
|
|
||||||
return 0, fmt.Errorf(
|
|
||||||
"cannot read content from non-file '%s'", n.Path())
|
|
||||||
}
|
|
||||||
if n.offset == nil {
|
|
||||||
return 0, fmt.Errorf("cannot read from closed file '%s'", n.Path())
|
|
||||||
}
|
|
||||||
|
|
||||||
rest := n.content[*n.offset:]
|
|
||||||
if len(d) < len(rest) {
|
|
||||||
rest = rest[:len(d)]
|
|
||||||
} else {
|
|
||||||
err = io.EOF
|
|
||||||
}
|
|
||||||
copy(d, rest)
|
|
||||||
*n.offset += len(rest)
|
|
||||||
return len(rest), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write saves the contents of the argument to the file node.
|
|
||||||
func (n *fsNode) Write(p []byte) (c int, err error) {
|
|
||||||
if n.isNodeADir() {
|
|
||||||
return 0, fmt.Errorf(
|
|
||||||
"cannot write content to non-file '%s'", n.Path())
|
|
||||||
}
|
|
||||||
if n.offset == nil {
|
|
||||||
return 0, fmt.Errorf("cannot write to closed file '%s'", n.Path())
|
|
||||||
}
|
|
||||||
n.content = append(n.content[:*n.offset], p...)
|
|
||||||
*n.offset = len(n.content)
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContentMatches returns true if v matches fake file's content.
|
|
||||||
func (n *fsNode) ContentMatches(v []byte) bool {
|
|
||||||
return bytes.Equal(v, n.content)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContent the content of a fake file.
|
|
||||||
func (n *fsNode) GetContent() []byte {
|
|
||||||
return n.content
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stat returns an instance of FileInfo.
|
|
||||||
func (n *fsNode) Stat() (os.FileInfo, error) {
|
|
||||||
return fileInfo{node: n}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk implements FileSystem.
|
|
||||||
func (n *fsNode) Walk(path string, walkFn filepath.WalkFunc) error {
|
|
||||||
result, err := n.Find(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
return fmt.Errorf("cannot find '%s' to walk it", path)
|
|
||||||
}
|
|
||||||
return result.WalkMe(walkFn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk runs the given walkFn on each node.
|
|
||||||
func (n *fsNode) WalkMe(walkFn filepath.WalkFunc) error {
|
|
||||||
fi, err := n.Stat()
|
|
||||||
// always visit self first
|
|
||||||
err = walkFn(n.Path(), fi, err)
|
|
||||||
if !n.isNodeADir() {
|
|
||||||
// it's a file, so nothing more to do
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// process self as a directory
|
|
||||||
if err == filepath.SkipDir {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Walk is supposed to visit in lexical order.
|
|
||||||
for _, k := range n.sortedDirEntries() {
|
|
||||||
if err := n.dir[k].WalkMe(walkFn); err != nil {
|
|
||||||
if err == filepath.SkipDir {
|
|
||||||
// stop processing this directory
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// bail out completely
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *fsNode) sortedDirEntries() []string {
|
|
||||||
keys := make([]string, len(n.dir))
|
|
||||||
i := 0
|
|
||||||
for k := range n.dir {
|
|
||||||
keys[i] = k
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
// FileCount returns a count of files.
|
|
||||||
// Directories, empty or otherwise, not counted.
|
|
||||||
func (n *fsNode) FileCount() int {
|
|
||||||
count := 0
|
|
||||||
n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *fsNode) DebugPrint() {
|
|
||||||
n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("err '%v' at path %q\n", err, path)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
if info.Size() == 0 {
|
|
||||||
fmt.Println("empty dir: " + path)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Println(" file: " + path)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var legalFileNamePattern = regexp.MustCompile("^[a-zA-Z0-9-_.]+$")
|
|
||||||
|
|
||||||
// This rules enforced here should be simpler and tighter
|
|
||||||
// than what's allowed on a real OS.
|
|
||||||
// Should be fine for testing or in-memory purposes.
|
|
||||||
func isLegalFileNameForCreation(n string) bool {
|
|
||||||
if n == "" || n == SelfDir || !legalFileNamePattern.MatchString(n) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return !strings.Contains(n, ParentDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegExpGlob returns a list of file paths matching the regexp.
|
|
||||||
// Excludes directories.
|
|
||||||
func (n *fsNode) RegExpGlob(pattern string) ([]string, error) {
|
|
||||||
var result []string
|
|
||||||
var expression = regexp.MustCompile(pattern)
|
|
||||||
err := n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
if expression.MatchString(path) {
|
|
||||||
result = append(result, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sort.Strings(result)
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Glob implements FileSystem.
|
|
||||||
// Glob returns the list of file paths matching
|
|
||||||
// per filepath.Match semantics, i.e. unlike RegExpGlob,
|
|
||||||
// Match("foo/a*") will not match sub-sub directories of foo.
|
|
||||||
// This is how /bin/ls behaves.
|
|
||||||
func (n *fsNode) Glob(pattern string) ([]string, error) {
|
|
||||||
var result []string
|
|
||||||
err := n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
match, err := filepath.Match(pattern, path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if match {
|
|
||||||
result = append(result, path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sort.Strings(result)
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
@@ -1,854 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package filesys
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
const content = `
|
|
||||||
Lorem ipsum dolor sit amet,
|
|
||||||
consectetur adipiscing elit,
|
|
||||||
sed do eiusmod tempor incididunt
|
|
||||||
ut labore et dolore magna aliqua.
|
|
||||||
`
|
|
||||||
const shortContent = "hi"
|
|
||||||
|
|
||||||
var topCases = []pathCase{
|
|
||||||
{
|
|
||||||
what: "dotdot",
|
|
||||||
arg: ParentDir,
|
|
||||||
errStr: "illegal name '..' in file creation",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "empty",
|
|
||||||
arg: "",
|
|
||||||
name: "",
|
|
||||||
errStr: "illegal name '.' in file creation",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "simple",
|
|
||||||
arg: "bob",
|
|
||||||
name: "bob",
|
|
||||||
path: "bob",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "longer",
|
|
||||||
arg: filepath.Join("longer", "bob"),
|
|
||||||
name: "bob",
|
|
||||||
path: filepath.Join("longer", "bob"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "longer yet",
|
|
||||||
arg: filepath.Join("longer", "foo", "bar", "beans", "bob"),
|
|
||||||
name: "bob",
|
|
||||||
path: filepath.Join("longer", "foo", "bar", "beans", "bob"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "tricky",
|
|
||||||
arg: filepath.Join("bob", ParentDir, "sally"),
|
|
||||||
name: "sally",
|
|
||||||
path: "sally",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "trickier",
|
|
||||||
arg: filepath.Join("bob", "sally", ParentDir, ParentDir, "jean"),
|
|
||||||
name: "jean",
|
|
||||||
path: "jean",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeEmptyDirInMemory(t *testing.T) {
|
|
||||||
n := MakeEmptyDirInMemory()
|
|
||||||
if !n.isNodeADir() {
|
|
||||||
t.Fatalf("not a directory")
|
|
||||||
}
|
|
||||||
if n.Size() != 0 {
|
|
||||||
t.Fatalf("unexpected size %d", n.Size())
|
|
||||||
}
|
|
||||||
if n.Name() != "" {
|
|
||||||
t.Fatalf("unexpected name '%s'", n.Name())
|
|
||||||
}
|
|
||||||
if n.Path() != "" {
|
|
||||||
t.Fatalf("unexpected path '%s'", n.Path())
|
|
||||||
}
|
|
||||||
runBasicOperations(
|
|
||||||
t, "MakeEmptyDirInMemory", false, topCases, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMakeFsInMemory(t *testing.T) {
|
|
||||||
runBasicOperations(
|
|
||||||
t, "MakeFsInMemory", true, topCases, MakeFsInMemory())
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:gocyclo
|
|
||||||
func runBasicOperations(
|
|
||||||
t *testing.T, tName string, isFSysRooted bool,
|
|
||||||
cases []pathCase, fSys FileSystem) {
|
|
||||||
for _, c := range cases {
|
|
||||||
err := fSys.WriteFile(c.arg, []byte(content))
|
|
||||||
if c.errStr != "" {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("%s; expected error writing to '%s'!", c.what, c.arg)
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), c.errStr) {
|
|
||||||
t.Fatalf("%s; expected err containing '%s', got '%v'",
|
|
||||||
c.what, c.errStr, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
|
||||||
}
|
|
||||||
if !fSys.Exists(c.path) {
|
|
||||||
t.Fatalf("%s; expect existence of '%s'", c.what, c.path)
|
|
||||||
}
|
|
||||||
stuff, err := fSys.ReadFile(c.path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
|
||||||
}
|
|
||||||
if string(stuff) != content {
|
|
||||||
t.Fatalf("%s; unexpected content '%s'", c.what, stuff)
|
|
||||||
}
|
|
||||||
f, err := fSys.Open(c.arg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
|
||||||
}
|
|
||||||
fi, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
|
||||||
}
|
|
||||||
if fi.Name() != c.name {
|
|
||||||
t.Fatalf("%s; expected name '%s', got '%s'", c.what, c.name, fi.Name())
|
|
||||||
}
|
|
||||||
buff, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
|
||||||
}
|
|
||||||
if string(buff) != content {
|
|
||||||
t.Fatalf("%s; unexpected buff '%s'", c.what, buff)
|
|
||||||
}
|
|
||||||
count, err := f.Write([]byte(shortContent))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
|
||||||
}
|
|
||||||
if count != len(shortContent) {
|
|
||||||
t.Fatalf("%s; unexpected count: %d", c.what, len(shortContent))
|
|
||||||
}
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
|
||||||
}
|
|
||||||
stuff, err = fSys.ReadFile(c.path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
|
||||||
}
|
|
||||||
both := content + shortContent
|
|
||||||
if string(stuff) != both {
|
|
||||||
t.Fatalf("%s; unexpected content '%s', expected '%s'", c.what, stuff, both)
|
|
||||||
}
|
|
||||||
if err := fSys.WriteFile(c.path, []byte(shortContent)); err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
|
||||||
}
|
|
||||||
stuff, err = fSys.ReadFile(c.path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
|
||||||
}
|
|
||||||
if string(stuff) != shortContent {
|
|
||||||
t.Fatalf("%s; unexpected content '%s', expected '%s'", c.what, stuff, shortContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var actualPaths []string
|
|
||||||
var err error
|
|
||||||
prefix := ""
|
|
||||||
{
|
|
||||||
root := SelfDir
|
|
||||||
if isFSysRooted {
|
|
||||||
root = Separator
|
|
||||||
prefix = Separator
|
|
||||||
}
|
|
||||||
err = fSys.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("err '%v' at path %q\n", err, path)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !info.IsDir() {
|
|
||||||
actualPaths = append(actualPaths, path)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
var expectedPaths []string
|
|
||||||
for _, c := range cases {
|
|
||||||
if c.errStr == "" {
|
|
||||||
expectedPaths = append(expectedPaths, prefix+c.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(expectedPaths)
|
|
||||||
assertEqualStringSlices(t, expectedPaths, actualPaths, tName)
|
|
||||||
}
|
|
||||||
|
|
||||||
type pathCase struct {
|
|
||||||
what string
|
|
||||||
arg string
|
|
||||||
name string
|
|
||||||
path string
|
|
||||||
errStr string
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddDir(t *testing.T) {
|
|
||||||
cases := []pathCase{
|
|
||||||
{
|
|
||||||
what: "dotdot",
|
|
||||||
arg: ParentDir,
|
|
||||||
errStr: "cannot add a directory above ''",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "empty",
|
|
||||||
arg: "",
|
|
||||||
name: "",
|
|
||||||
path: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "simple",
|
|
||||||
arg: "bob",
|
|
||||||
name: "bob",
|
|
||||||
path: "bob",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "longer",
|
|
||||||
arg: filepath.Join("longer", "bob"),
|
|
||||||
name: "bob",
|
|
||||||
path: filepath.Join("longer", "bob"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "longer yet",
|
|
||||||
arg: filepath.Join("longer", "foo", "bar", "beans", "bob"),
|
|
||||||
name: "bob",
|
|
||||||
path: filepath.Join("longer", "foo", "bar", "beans", "bob"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "tricky",
|
|
||||||
arg: filepath.Join("bob", ParentDir, "sally"),
|
|
||||||
name: "sally",
|
|
||||||
path: "sally",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "trickier",
|
|
||||||
arg: filepath.Join("bob", "sally", ParentDir, ParentDir, "jean"),
|
|
||||||
name: "jean",
|
|
||||||
path: "jean",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, c := range cases {
|
|
||||||
n := MakeEmptyDirInMemory()
|
|
||||||
f, err := n.AddDir(c.arg)
|
|
||||||
if c.errStr != "" {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("%s; expected error!", c.what)
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), c.errStr) {
|
|
||||||
t.Fatalf(
|
|
||||||
"%s; expected error with '%s', got '%v'",
|
|
||||||
c.what, c.errStr, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", c.what, err)
|
|
||||||
}
|
|
||||||
checkNode(t, c.what, f, c.name, 0, true, c.path)
|
|
||||||
checkOsStat(t, c.what, f, f.Name(), 0, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bagOfCases = []pathCase{
|
|
||||||
{
|
|
||||||
what: "empty",
|
|
||||||
arg: "",
|
|
||||||
errStr: "illegal name '.' in file creation",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "simple",
|
|
||||||
arg: "bob",
|
|
||||||
name: "bob",
|
|
||||||
path: "bob",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "longer",
|
|
||||||
arg: filepath.Join("longer", "bob"),
|
|
||||||
name: "bob",
|
|
||||||
path: filepath.Join("longer", "bob"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "longer",
|
|
||||||
arg: filepath.Join("longer", "sally"),
|
|
||||||
name: "sally",
|
|
||||||
path: filepath.Join("longer", "sally"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "even longer",
|
|
||||||
arg: filepath.Join("longer", "than", "the", "other", "bob"),
|
|
||||||
name: "bob",
|
|
||||||
path: filepath.Join("longer", "than", "the", "other", "bob"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "even longer",
|
|
||||||
arg: filepath.Join("even", "much", "longer", "than", "the", "other", "bob"),
|
|
||||||
name: "bob",
|
|
||||||
path: filepath.Join("even", "much", "longer", "than", "the", "other", "bob"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAddFile(t *testing.T) {
|
|
||||||
n := MakeEmptyDirInMemory()
|
|
||||||
if n.FileCount() != 0 {
|
|
||||||
t.Fatalf("expected no files, got %d", n.FileCount())
|
|
||||||
}
|
|
||||||
expectedFileCount := 0
|
|
||||||
for _, c := range bagOfCases {
|
|
||||||
f, err := n.AddFile(c.arg, []byte(content))
|
|
||||||
if c.errStr != "" {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("%s; expected error!", c.what)
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), c.errStr) {
|
|
||||||
t.Fatalf("%s; expected err containing '%s', got '%v'",
|
|
||||||
c.what, c.errStr, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error %v", c.what, err)
|
|
||||||
}
|
|
||||||
checkNode(t, c.what, f, c.name, len(content), false, c.path)
|
|
||||||
checkOsStat(t, c.what, f, f.Name(), len(content), false)
|
|
||||||
|
|
||||||
result, err := n.Find(c.arg)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected find error %v", c.what, err)
|
|
||||||
}
|
|
||||||
if result != f {
|
|
||||||
t.Fatalf("%s; unexpected find result %v", c.what, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err = n.Find(filepath.Join("longer", "bogus"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected find error %v", c.what, err)
|
|
||||||
}
|
|
||||||
if result != nil {
|
|
||||||
t.Fatalf("%s; unexpected find result %v", c.what, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedFileCount++
|
|
||||||
fc := n.FileCount()
|
|
||||||
if fc != expectedFileCount {
|
|
||||||
t.Fatalf("expected file count %d, got %d",
|
|
||||||
expectedFileCount, fc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkNode(
|
|
||||||
t *testing.T, what string, f *fsNode, name string,
|
|
||||||
size int, isDir bool, path string) {
|
|
||||||
if f.isNodeADir() != isDir {
|
|
||||||
t.Fatalf("%s; unexpected isNodeADir = %v", what, f.isNodeADir())
|
|
||||||
}
|
|
||||||
if f.Size() != int64(size) {
|
|
||||||
t.Fatalf("%s; unexpected size %d", what, f.Size())
|
|
||||||
}
|
|
||||||
if name != f.Name() {
|
|
||||||
t.Fatalf("%s; expected name '%s', got '%s'", what, name, f.Name())
|
|
||||||
}
|
|
||||||
if path != f.Path() {
|
|
||||||
t.Fatalf("%s; expected path '%s', got '%s'", what, path, f.Path())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkOsStat(
|
|
||||||
t *testing.T, what string, f File, name string,
|
|
||||||
size int, isDir bool) {
|
|
||||||
info, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected stat error %v", what, err)
|
|
||||||
}
|
|
||||||
if info.IsDir() != isDir {
|
|
||||||
t.Fatalf("%s; unexpected info.isNodeADir = %v", what, info.IsDir())
|
|
||||||
}
|
|
||||||
if info.Size() != int64(size) {
|
|
||||||
t.Fatalf("%s; unexpected info.size %d", what, info.Size())
|
|
||||||
}
|
|
||||||
if info.Name() != name {
|
|
||||||
t.Fatalf("%s; expected name '%s', got info.Name '%s'", what, name, info.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bunchOfFiles = []struct {
|
|
||||||
path string
|
|
||||||
addAsDir bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "e", "a", "c", "g"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("z", "r", "a", "b", "g"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "q", "a", "c", "g"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "a", "a", "m", "g"),
|
|
||||||
addAsDir: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "w"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "d", "a", "c", "m"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "d", "z"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "d", "y"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "d", "ignore", "c", "n"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "d", "x"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "d", "ignore", "c", "o"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "d", "ignore", "c", "m"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "d", "a", "c", "i"),
|
|
||||||
addAsDir: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("x"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("y"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "d", "a", "c", "i", "beans"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "d", "a", "c", "r", "w"),
|
|
||||||
addAsDir: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: filepath.Join("b", "d", "a", "c", "u"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeLoadedFileTree(t *testing.T) *fsNode {
|
|
||||||
n := MakeEmptyDirInMemory()
|
|
||||||
var err error
|
|
||||||
expectedFileCount := 0
|
|
||||||
for _, item := range bunchOfFiles {
|
|
||||||
if item.addAsDir {
|
|
||||||
_, err = n.AddDir(item.path)
|
|
||||||
} else {
|
|
||||||
_, err = n.AddFile(item.path, []byte(content))
|
|
||||||
expectedFileCount++
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fc := n.FileCount()
|
|
||||||
if fc != expectedFileCount {
|
|
||||||
t.Fatalf("expected file count %d, got %d",
|
|
||||||
expectedFileCount, fc)
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWalkMe(t *testing.T) {
|
|
||||||
n := makeLoadedFileTree(t)
|
|
||||||
var actualPaths []string
|
|
||||||
err := n.WalkMe(func(path string, info os.FileInfo, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("err '%v' at path %q\n", err, path)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if info.IsDir() {
|
|
||||||
if info.Name() == "ignore" {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
actualPaths = append(actualPaths, path)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %v", err)
|
|
||||||
}
|
|
||||||
var expectedPaths []string
|
|
||||||
for _, c := range bunchOfFiles {
|
|
||||||
if !c.addAsDir && !strings.Contains(c.path, "ignore") {
|
|
||||||
expectedPaths = append(expectedPaths, c.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(expectedPaths)
|
|
||||||
assertEqualStringSlices(t, expectedPaths, actualPaths, "testWalkMe")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRemove(t *testing.T) {
|
|
||||||
n := makeLoadedFileTree(t)
|
|
||||||
orgCount := n.FileCount()
|
|
||||||
|
|
||||||
// Remove the "ignore" directory and everything below it.
|
|
||||||
path := filepath.Join("b", "d", "ignore")
|
|
||||||
result, err := n.Find(path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error %v", path, err)
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
t.Fatalf("%s; expected to find '%s'", path, path)
|
|
||||||
}
|
|
||||||
if !result.isNodeADir() {
|
|
||||||
t.Fatalf("%s; expected to find a directory", path)
|
|
||||||
}
|
|
||||||
err = result.Remove()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unable to remove: %v", path, err)
|
|
||||||
}
|
|
||||||
result, err = n.Find(path)
|
|
||||||
if err != nil {
|
|
||||||
// Just because it's gone doesn't mean error.
|
|
||||||
t.Fatalf("%s; unexpected error %v", path, err)
|
|
||||||
}
|
|
||||||
if result != nil {
|
|
||||||
t.Fatalf("%s; should not have been able to find '%s'", path, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// There were three files below "ignore".
|
|
||||||
orgCount -= 3
|
|
||||||
|
|
||||||
// Now drop one more for a total of four dropped.
|
|
||||||
result, _ = n.Find(filepath.Join("y"))
|
|
||||||
err = result.Remove()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unable to remove: %v", path, err)
|
|
||||||
}
|
|
||||||
orgCount -= 1
|
|
||||||
|
|
||||||
fc := n.FileCount()
|
|
||||||
if fc != orgCount {
|
|
||||||
t.Fatalf("expected file count %d, got %d",
|
|
||||||
orgCount, fc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExists(t *testing.T) {
|
|
||||||
n := makeLoadedFileTree(t)
|
|
||||||
path := filepath.Join("b", "d", "a")
|
|
||||||
if !n.Exists(path) {
|
|
||||||
t.Fatalf("expected existence at %s", path)
|
|
||||||
}
|
|
||||||
if !n.IsDir(path) {
|
|
||||||
t.Fatalf("expected directory at %s", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegExpGlob(t *testing.T) {
|
|
||||||
n := makeLoadedFileTree(t)
|
|
||||||
expected := []string{
|
|
||||||
filepath.Join("b", "d", "a", "c", "i", "beans"),
|
|
||||||
filepath.Join("b", "d", "a", "c", "m"),
|
|
||||||
filepath.Join("b", "d", "a", "c", "u"),
|
|
||||||
filepath.Join("b", "d", "ignore", "c", "m"),
|
|
||||||
filepath.Join("b", "d", "ignore", "c", "n"),
|
|
||||||
filepath.Join("b", "d", "ignore", "c", "o"),
|
|
||||||
filepath.Join("b", "d", "x"),
|
|
||||||
filepath.Join("b", "d", "y"),
|
|
||||||
filepath.Join("b", "d", "z"),
|
|
||||||
}
|
|
||||||
paths, err := n.RegExpGlob("b/d/*")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("glob error: %v", err)
|
|
||||||
}
|
|
||||||
assertEqualStringSlices(t, expected, paths, "glob test")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlob(t *testing.T) {
|
|
||||||
n := makeLoadedFileTree(t)
|
|
||||||
expected := []string{
|
|
||||||
filepath.Join("b", "d", "x"),
|
|
||||||
filepath.Join("b", "d", "y"),
|
|
||||||
filepath.Join("b", "d", "z"),
|
|
||||||
}
|
|
||||||
paths, err := n.Glob("b/d/*")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("glob error: %v", err)
|
|
||||||
}
|
|
||||||
assertEqualStringSlices(t, expected, paths, "glob test")
|
|
||||||
}
|
|
||||||
|
|
||||||
func assertEqualStringSlices(t *testing.T, expected, actual []string, message string) {
|
|
||||||
t.Helper()
|
|
||||||
if len(expected) != len(actual) {
|
|
||||||
t.Fatalf(
|
|
||||||
"%s; unequal sizes; len(expected)=%d, len(actual)=%d\n%+v\n%+v\n",
|
|
||||||
message, len(expected), len(actual), expected, actual)
|
|
||||||
}
|
|
||||||
for i := range expected {
|
|
||||||
if expected[i] != actual[i] {
|
|
||||||
t.Fatalf(
|
|
||||||
"%s; unequal entries; expected=%s, actual=%s",
|
|
||||||
message, expected[i], actual[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFind(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
what string
|
|
||||||
arg string
|
|
||||||
expectDir bool
|
|
||||||
expectFile bool
|
|
||||||
errStr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
what: "garbage",
|
|
||||||
arg: "///1(*&SA",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "simple",
|
|
||||||
arg: "bob",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "no directory",
|
|
||||||
arg: filepath.Join("b", "rrrrrr"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "is a directory",
|
|
||||||
arg: filepath.Join("b", "d", "ignore"),
|
|
||||||
expectDir: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "longer, ending in file",
|
|
||||||
arg: filepath.Join("b", "d", "x"),
|
|
||||||
expectFile: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "moar longer, ending in file",
|
|
||||||
arg: filepath.Join("b", "d", "a", "c", "u"),
|
|
||||||
expectFile: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "directory",
|
|
||||||
arg: filepath.Join("b"),
|
|
||||||
expectDir: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Querying for the empty string could
|
|
||||||
// 1) be an error,
|
|
||||||
// 2) return no result (and no error) as with
|
|
||||||
// any illegal and therefore non-existent
|
|
||||||
// file name,
|
|
||||||
// 3) return the node itself, like running
|
|
||||||
// 'ls' with no argument.
|
|
||||||
// Going with option 2 (no result, no error),
|
|
||||||
// since at this low level it makes more sense
|
|
||||||
// if the results for the empty string query
|
|
||||||
// differ from the results for the "." query.
|
|
||||||
what: "empty name",
|
|
||||||
arg: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "self dir",
|
|
||||||
arg: SelfDir,
|
|
||||||
expectDir: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "parent dir - doesn't exist",
|
|
||||||
arg: ParentDir,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "many parents - doesn't exist",
|
|
||||||
arg: filepath.Join(ParentDir, ParentDir, ParentDir),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
n := makeLoadedFileTree(t)
|
|
||||||
for _, item := range cases {
|
|
||||||
result, err := n.Find(item.arg)
|
|
||||||
if item.errStr != "" {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("%s; expected error", item.what)
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), item.errStr) {
|
|
||||||
t.Fatalf("%s; expected err containing '%s', got '%v'",
|
|
||||||
item.what, item.errStr, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", item.what, err)
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
if item.expectDir {
|
|
||||||
t.Fatalf(
|
|
||||||
"%s; expected to find directory '%s'", item.what, item.arg)
|
|
||||||
}
|
|
||||||
if item.expectFile {
|
|
||||||
t.Fatalf(
|
|
||||||
"%s; expected to find file '%s'", item.what, item.arg)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if item.expectDir {
|
|
||||||
if !result.isNodeADir() {
|
|
||||||
t.Fatalf(
|
|
||||||
"%s; expected '%s' to be a directory", item.what, item.arg)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if item.expectFile {
|
|
||||||
if result.isNodeADir() {
|
|
||||||
t.Fatalf("%s; expected '%s' to be a file", item.what, item.arg)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.Fatalf(
|
|
||||||
"%s; expected nothing for '%s', but got '%s'",
|
|
||||||
item.what, item.arg, result.Path())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanedAbs(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
what string
|
|
||||||
full string
|
|
||||||
cDir string
|
|
||||||
name string
|
|
||||||
errStr string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
what: "empty",
|
|
||||||
full: "",
|
|
||||||
errStr: "doesn't exist",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "simple",
|
|
||||||
full: "bob",
|
|
||||||
errStr: "'bob' doesn't exist",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "no directory",
|
|
||||||
full: filepath.Join("b", "rrrrrr"),
|
|
||||||
errStr: "'b/rrrrrr' doesn't exist",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "longer, ending in file",
|
|
||||||
full: filepath.Join("b", "d", "x"),
|
|
||||||
cDir: filepath.Join("b", "d"),
|
|
||||||
name: "x",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "moar longer, ending in file",
|
|
||||||
full: filepath.Join("b", "d", "a", "c", "u"),
|
|
||||||
cDir: filepath.Join("b", "d", "a", "c"),
|
|
||||||
name: "u",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
what: "directory",
|
|
||||||
full: filepath.Join("b", "d"),
|
|
||||||
cDir: filepath.Join("b", "d"),
|
|
||||||
name: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
n := makeLoadedFileTree(t)
|
|
||||||
for _, item := range cases {
|
|
||||||
cDir, name, err := n.CleanedAbs(item.full)
|
|
||||||
if item.errStr != "" {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("%s; expected error", item.what)
|
|
||||||
}
|
|
||||||
if !strings.Contains(err.Error(), item.errStr) {
|
|
||||||
t.Fatalf("%s; expected err containing '%s', got '%v'",
|
|
||||||
item.what, item.errStr, err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("%s; unexpected error: %v", item.what, err)
|
|
||||||
}
|
|
||||||
if cDir != ConfirmedDir(item.cDir) {
|
|
||||||
t.Fatalf("%s; expected cDir=%s, got '%s'", item.what, item.cDir, cDir)
|
|
||||||
}
|
|
||||||
if name != item.name {
|
|
||||||
t.Fatalf("%s; expected name=%s, got '%s'", item.what, item.name, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFileOps(t *testing.T) {
|
|
||||||
const path = "foo.txt"
|
|
||||||
content := strings.Repeat("longest content", 100)
|
|
||||||
|
|
||||||
fs := MakeFsInMemory()
|
|
||||||
f, err := fs.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := fs.Open(path); err == nil {
|
|
||||||
t.Fatalf("expected already opened error, got nil")
|
|
||||||
}
|
|
||||||
if _, err := fmt.Fprint(f, content); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := f.Close(); err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if err := f.Close(); err == nil {
|
|
||||||
t.Fatalf("expected already closed error, got nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err = fs.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
for {
|
|
||||||
buf := make([]byte, rand.Intn(10))
|
|
||||||
n, err := f.Read(buf)
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if content[:n] != string(buf[:n]) {
|
|
||||||
t.Fatalf("unexpected read: expected %q got %q", content[:n], buf[:n])
|
|
||||||
}
|
|
||||||
content = content[n:]
|
|
||||||
if err != io.EOF {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(content) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
t.Fatalf("unexpected EOF: remaining %d bytes", len(content))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package filesys_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "sigs.k8s.io/kustomize/api/filesys"
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeTestDir(t *testing.T) (FileSystem, string) {
|
|
||||||
fSys := MakeFsOnDisk()
|
|
||||||
td, err := ioutil.TempDir("", "kustomize_testing_dir")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %s", err)
|
|
||||||
}
|
|
||||||
testDir, err := filepath.EvalSymlinks(td)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %s", err)
|
|
||||||
}
|
|
||||||
if !fSys.Exists(testDir) {
|
|
||||||
t.Fatalf("expected existence")
|
|
||||||
}
|
|
||||||
if !fSys.IsDir(testDir) {
|
|
||||||
t.Fatalf("expected directory")
|
|
||||||
}
|
|
||||||
return fSys, testDir
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanedAbs_1(t *testing.T) {
|
|
||||||
fSys, testDir := makeTestDir(t)
|
|
||||||
defer os.RemoveAll(testDir)
|
|
||||||
|
|
||||||
d, f, err := fSys.CleanedAbs("")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
if d.String() != wd {
|
|
||||||
t.Fatalf("unexpected d=%s", d)
|
|
||||||
}
|
|
||||||
if f != "" {
|
|
||||||
t.Fatalf("unexpected f=%s", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanedAbs_2(t *testing.T) {
|
|
||||||
fSys, testDir := makeTestDir(t)
|
|
||||||
defer os.RemoveAll(testDir)
|
|
||||||
|
|
||||||
d, f, err := fSys.CleanedAbs("/")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
if d != "/" {
|
|
||||||
t.Fatalf("unexpected d=%s", d)
|
|
||||||
}
|
|
||||||
if f != "" {
|
|
||||||
t.Fatalf("unexpected f=%s", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanedAbs_3(t *testing.T) {
|
|
||||||
fSys, testDir := makeTestDir(t)
|
|
||||||
defer os.RemoveAll(testDir)
|
|
||||||
|
|
||||||
err := fSys.WriteFile(
|
|
||||||
filepath.Join(testDir, "foo"), []byte(`foo`))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, f, err := fSys.CleanedAbs(filepath.Join(testDir, "foo"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
if d.String() != testDir {
|
|
||||||
t.Fatalf("unexpected d=%s", d)
|
|
||||||
}
|
|
||||||
if f != "foo" {
|
|
||||||
t.Fatalf("unexpected f=%s", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCleanedAbs_4(t *testing.T) {
|
|
||||||
fSys, testDir := makeTestDir(t)
|
|
||||||
defer os.RemoveAll(testDir)
|
|
||||||
|
|
||||||
err := fSys.MkdirAll(filepath.Join(testDir, "d1", "d2"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
err = fSys.WriteFile(
|
|
||||||
filepath.Join(testDir, "d1", "d2", "bar"),
|
|
||||||
[]byte(`bar`))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, f, err := fSys.CleanedAbs(
|
|
||||||
filepath.Join(testDir, "d1", "d2"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
if d.String() != filepath.Join(testDir, "d1", "d2") {
|
|
||||||
t.Fatalf("unexpected d=%s", d)
|
|
||||||
}
|
|
||||||
if f != "" {
|
|
||||||
t.Fatalf("unexpected f=%s", f)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, f, err = fSys.CleanedAbs(
|
|
||||||
filepath.Join(testDir, "d1", "d2", "bar"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err=%v", err)
|
|
||||||
}
|
|
||||||
if d.String() != filepath.Join(testDir, "d1", "d2") {
|
|
||||||
t.Fatalf("unexpected d=%s", d)
|
|
||||||
}
|
|
||||||
if f != "bar" {
|
|
||||||
t.Fatalf("unexpected f=%s", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadFilesRealFS(t *testing.T) {
|
|
||||||
fSys, testDir := makeTestDir(t)
|
|
||||||
defer os.RemoveAll(testDir)
|
|
||||||
|
|
||||||
err := fSys.WriteFile(path.Join(testDir, "foo"), []byte(`foo`))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %s", err)
|
|
||||||
}
|
|
||||||
if !fSys.Exists(path.Join(testDir, "foo")) {
|
|
||||||
t.Fatalf("expected foo")
|
|
||||||
}
|
|
||||||
if fSys.IsDir(path.Join(testDir, "foo")) {
|
|
||||||
t.Fatalf("expected foo not to be a directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = fSys.WriteFile(path.Join(testDir, "bar"), []byte(`bar`))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
files, err := fSys.Glob(path.Join("testDir", "*"))
|
|
||||||
expected := []string{
|
|
||||||
path.Join(testDir, "bar"),
|
|
||||||
path.Join(testDir, "foo"),
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("expected no error")
|
|
||||||
}
|
|
||||||
if reflect.DeepEqual(files, expected) {
|
|
||||||
t.Fatalf("incorrect files found by glob: %v", files)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package filesys
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RootedPath returns a rooted path, e.g. "/foo/bar" as
|
|
||||||
// opposed to "foo/bar".
|
|
||||||
func RootedPath(elem ...string) string {
|
|
||||||
return Separator + filepath.Join(elem...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StripTrailingSeps trims trailing filepath separators from input.
|
|
||||||
func StripTrailingSeps(s string) string {
|
|
||||||
k := len(s)
|
|
||||||
for k > 0 && s[k-1] == filepath.Separator {
|
|
||||||
k--
|
|
||||||
}
|
|
||||||
return s[:k]
|
|
||||||
}
|
|
||||||
|
|
||||||
// StripLeadingSeps trims leading filepath separators from input.
|
|
||||||
func StripLeadingSeps(s string) string {
|
|
||||||
k := 0
|
|
||||||
for k < len(s) && s[k] == filepath.Separator {
|
|
||||||
k++
|
|
||||||
}
|
|
||||||
return s[k:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathSplit converts a file path to a slice of string.
|
|
||||||
// If the path is absolute (if the path has a leading slash),
|
|
||||||
// then the first entry in the result is an empty string.
|
|
||||||
// Desired: path == PathJoin(PathSplit(path))
|
|
||||||
func PathSplit(incoming string) []string {
|
|
||||||
if incoming == "" {
|
|
||||||
return []string{}
|
|
||||||
}
|
|
||||||
dir, path := filepath.Split(incoming)
|
|
||||||
if dir == string(os.PathSeparator) {
|
|
||||||
if path == "" {
|
|
||||||
return []string{""}
|
|
||||||
}
|
|
||||||
return []string{"", path}
|
|
||||||
}
|
|
||||||
dir = strings.TrimSuffix(dir, string(os.PathSeparator))
|
|
||||||
if dir == "" {
|
|
||||||
return []string{path}
|
|
||||||
}
|
|
||||||
return append(PathSplit(dir), path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathJoin converts a slice of string to a file path.
|
|
||||||
// If the first entry is an empty string, then the returned
|
|
||||||
// path is absolute (it has a leading slash).
|
|
||||||
// Desired: path == PathJoin(PathSplit(path))
|
|
||||||
func PathJoin(incoming []string) string {
|
|
||||||
if len(incoming) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if incoming[0] == "" {
|
|
||||||
return string(os.PathSeparator) + filepath.Join(incoming[1:]...)
|
|
||||||
}
|
|
||||||
return filepath.Join(incoming...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsertPathPart inserts 'part' at position 'pos' in the given filepath.
|
|
||||||
// The first position is 0.
|
|
||||||
//
|
|
||||||
// E.g. if part == 'PEACH'
|
|
||||||
//
|
|
||||||
// OLD : NEW : POS
|
|
||||||
// --------------------------------------------------------
|
|
||||||
// {empty} : PEACH : irrelevant
|
|
||||||
// / : /PEACH : irrelevant
|
|
||||||
// pie : PEACH/pie : 0 (or negative)
|
|
||||||
// /pie : /PEACH/pie : 0 (or negative)
|
|
||||||
// raw : raw/PEACH : 1 (or larger)
|
|
||||||
// /raw : /raw/PEACH : 1 (or larger)
|
|
||||||
// a/nice/warm/pie : a/nice/warm/PEACH/pie : 3
|
|
||||||
// /a/nice/warm/pie : /a/nice/warm/PEACH/pie : 3
|
|
||||||
//
|
|
||||||
// * An empty part results in no change.
|
|
||||||
//
|
|
||||||
// * Absolute paths get their leading '/' stripped, treated like
|
|
||||||
// relative paths, and the leading '/' is re-added on output.
|
|
||||||
// The meaning of pos is intentionally the same in either absolute or
|
|
||||||
// relative paths; if it weren't, this function could convert absolute
|
|
||||||
// paths to relative paths, which is not desirable.
|
|
||||||
//
|
|
||||||
// * For robustness (liberal input, conservative output) Pos values that
|
|
||||||
// that are too small (large) to index the split filepath result in a
|
|
||||||
// prefix (postfix) rather than an error. Use extreme position values
|
|
||||||
// to assure a prefix or postfix (e.g. 0 will always prefix, and
|
|
||||||
// 9999 will presumably always postfix).
|
|
||||||
func InsertPathPart(path string, pos int, part string) string {
|
|
||||||
if part == "" {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
parts := PathSplit(path)
|
|
||||||
if pos < 0 {
|
|
||||||
pos = 0
|
|
||||||
} else if pos > len(parts) {
|
|
||||||
pos = len(parts)
|
|
||||||
}
|
|
||||||
if len(parts) > 0 && parts[0] == "" && pos < len(parts) {
|
|
||||||
// An empty string at 0 indicates an absolute path, and means
|
|
||||||
// we must increment pos. This change means that a position
|
|
||||||
// specification has the same meaning in relative and absolute paths.
|
|
||||||
// E.g. in either the path 'a/b/c' or the path '/a/b/c',
|
|
||||||
// 'a' is at 0, 'b' is at 1 and 'c' is at 2, and inserting at
|
|
||||||
// zero means a new first field _without_ changing an absolute
|
|
||||||
// path to a relative path.
|
|
||||||
pos++
|
|
||||||
}
|
|
||||||
result := make([]string, len(parts)+1)
|
|
||||||
copy(result, parts[0:pos])
|
|
||||||
result[pos] = part
|
|
||||||
return PathJoin(append(result, parts[pos:]...))
|
|
||||||
}
|
|
||||||
@@ -1,374 +0,0 @@
|
|||||||
package filesys_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "sigs.k8s.io/kustomize/api/filesys"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Confirm behavior of filepath.Match
|
|
||||||
func TestFilePathMatch(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
pattern string
|
|
||||||
path string
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
pattern: "*e*",
|
|
||||||
path: "hey",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*e*",
|
|
||||||
path: "hay",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*e*",
|
|
||||||
path: filepath.Join("h", "e", "y"),
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*/e/*",
|
|
||||||
path: filepath.Join("h", "e", "y"),
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "h/e/*",
|
|
||||||
path: filepath.Join("h", "e", "y"),
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*/e/y",
|
|
||||||
path: filepath.Join("h", "e", "y"),
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*/*/*",
|
|
||||||
path: filepath.Join("h", "e", "y"),
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*/*/*",
|
|
||||||
path: filepath.Join("h", "e", "y", "there"),
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: "*/*/*/t*e",
|
|
||||||
path: filepath.Join("h", "e", "y", "there"),
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, item := range cases {
|
|
||||||
match, err := filepath.Match(item.pattern, item.path)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected err: %v", err)
|
|
||||||
}
|
|
||||||
if match != item.expected {
|
|
||||||
t.Fatalf("'%s' '%s' %v\n", item.pattern, item.path, match)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm behavior of filepath.Split
|
|
||||||
func TestFilePathSplit(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
full string
|
|
||||||
dir string
|
|
||||||
file string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
full: "",
|
|
||||||
dir: "",
|
|
||||||
file: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: SelfDir,
|
|
||||||
dir: "",
|
|
||||||
file: SelfDir,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "rabbit.jpg",
|
|
||||||
dir: "",
|
|
||||||
file: "rabbit.jpg",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/",
|
|
||||||
dir: "/",
|
|
||||||
file: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/beans",
|
|
||||||
dir: "/",
|
|
||||||
file: "beans",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/home/foo/bar",
|
|
||||||
dir: "/home/foo/",
|
|
||||||
file: "bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/usr/local/",
|
|
||||||
dir: "/usr/local/",
|
|
||||||
file: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/usr//local//go",
|
|
||||||
dir: "/usr//local//",
|
|
||||||
file: "go",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, p := range cases {
|
|
||||||
dir, file := filepath.Split(p.full)
|
|
||||||
if dir != p.dir || file != p.file {
|
|
||||||
t.Fatalf(
|
|
||||||
"in '%s',\ngot dir='%s' (expected '%s'),\n got file='%s' (expected %s).",
|
|
||||||
p.full, dir, p.dir, file, p.file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPathSplitAndJoin(t *testing.T) {
|
|
||||||
cases := map[string]struct {
|
|
||||||
original string
|
|
||||||
expected []string
|
|
||||||
}{
|
|
||||||
"Empty": {
|
|
||||||
original: "",
|
|
||||||
expected: []string{},
|
|
||||||
},
|
|
||||||
"One": {
|
|
||||||
original: "hello",
|
|
||||||
expected: []string{"hello"},
|
|
||||||
},
|
|
||||||
"Two": {
|
|
||||||
original: "hello/there",
|
|
||||||
expected: []string{"hello", "there"},
|
|
||||||
},
|
|
||||||
"Three": {
|
|
||||||
original: "hello/my/friend",
|
|
||||||
expected: []string{"hello", "my", "friend"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for n, c := range cases {
|
|
||||||
f := func(t *testing.T, original string, expected []string) {
|
|
||||||
actual := PathSplit(original)
|
|
||||||
if len(actual) != len(expected) {
|
|
||||||
t.Fatalf(
|
|
||||||
"expected len %d, got len %d",
|
|
||||||
len(expected), len(actual))
|
|
||||||
}
|
|
||||||
for i := range expected {
|
|
||||||
if expected[i] != actual[i] {
|
|
||||||
t.Fatalf(
|
|
||||||
"at i=%d, expected '%s', got '%s'",
|
|
||||||
i, expected[i], actual[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
joined := PathJoin(actual)
|
|
||||||
if joined != original {
|
|
||||||
t.Fatalf(
|
|
||||||
"when rejoining, expected '%s', got '%s'",
|
|
||||||
original, joined)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Run("relative"+n, func(t *testing.T) {
|
|
||||||
f(t, c.original, c.expected)
|
|
||||||
})
|
|
||||||
t.Run("absolute"+n, func(t *testing.T) {
|
|
||||||
f(t,
|
|
||||||
string(os.PathSeparator)+c.original,
|
|
||||||
append([]string{""}, c.expected...))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInsertPathPart(t *testing.T) {
|
|
||||||
cases := map[string]struct {
|
|
||||||
original string
|
|
||||||
pos int
|
|
||||||
part string
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
"rootOne": {
|
|
||||||
original: "/",
|
|
||||||
pos: 0,
|
|
||||||
part: "___",
|
|
||||||
expected: "/___",
|
|
||||||
},
|
|
||||||
"rootTwo": {
|
|
||||||
original: "/",
|
|
||||||
pos: 444,
|
|
||||||
part: "___",
|
|
||||||
expected: "/___",
|
|
||||||
},
|
|
||||||
"rootedFirst": {
|
|
||||||
original: "/apple",
|
|
||||||
pos: 0,
|
|
||||||
part: "___",
|
|
||||||
expected: "/___/apple",
|
|
||||||
},
|
|
||||||
"rootedSecond": {
|
|
||||||
original: "/apple",
|
|
||||||
pos: 444,
|
|
||||||
part: "___",
|
|
||||||
expected: "/apple/___",
|
|
||||||
},
|
|
||||||
"rootedThird": {
|
|
||||||
original: "/apple/banana",
|
|
||||||
pos: 444,
|
|
||||||
part: "___",
|
|
||||||
expected: "/apple/banana/___",
|
|
||||||
},
|
|
||||||
"emptyLow": {
|
|
||||||
original: "",
|
|
||||||
pos: -3,
|
|
||||||
part: "___",
|
|
||||||
expected: "___",
|
|
||||||
},
|
|
||||||
"emptyHigh": {
|
|
||||||
original: "",
|
|
||||||
pos: 444,
|
|
||||||
part: "___",
|
|
||||||
expected: "___",
|
|
||||||
},
|
|
||||||
"peachPie": {
|
|
||||||
original: "a/nice/warm/pie",
|
|
||||||
pos: 3,
|
|
||||||
part: "PEACH",
|
|
||||||
expected: "a/nice/warm/PEACH/pie",
|
|
||||||
},
|
|
||||||
"rootedPeachPie": {
|
|
||||||
original: "/a/nice/warm/pie",
|
|
||||||
pos: 3,
|
|
||||||
part: "PEACH",
|
|
||||||
expected: "/a/nice/warm/PEACH/pie",
|
|
||||||
},
|
|
||||||
"longStart": {
|
|
||||||
original: "a/b/c/d/e/f",
|
|
||||||
pos: 0,
|
|
||||||
part: "___",
|
|
||||||
expected: "___/a/b/c/d/e/f",
|
|
||||||
},
|
|
||||||
"rootedLongStart": {
|
|
||||||
original: "/a/b/c/d/e/f",
|
|
||||||
pos: 0,
|
|
||||||
part: "___",
|
|
||||||
expected: "/___/a/b/c/d/e/f",
|
|
||||||
},
|
|
||||||
"longMiddle": {
|
|
||||||
original: "a/b/c/d/e/f",
|
|
||||||
pos: 3,
|
|
||||||
part: "___",
|
|
||||||
expected: "a/b/c/___/d/e/f",
|
|
||||||
},
|
|
||||||
"rootedLongMiddle": {
|
|
||||||
original: "/a/b/c/d/e/f",
|
|
||||||
pos: 3,
|
|
||||||
part: "___",
|
|
||||||
expected: "/a/b/c/___/d/e/f",
|
|
||||||
},
|
|
||||||
"longEnd": {
|
|
||||||
original: "a/b/c/d/e/f",
|
|
||||||
pos: 444,
|
|
||||||
part: "___",
|
|
||||||
expected: "a/b/c/d/e/f/___",
|
|
||||||
},
|
|
||||||
"rootedLongEnd": {
|
|
||||||
original: "/a/b/c/d/e/f",
|
|
||||||
pos: 444,
|
|
||||||
part: "___",
|
|
||||||
expected: "/a/b/c/d/e/f/___",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for n, c := range cases {
|
|
||||||
t.Run(n, func(t *testing.T) {
|
|
||||||
actual := InsertPathPart(c.original, c.pos, c.part)
|
|
||||||
if actual != c.expected {
|
|
||||||
t.Fatalf("expected '%s', got '%s'", c.expected, actual)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStripTrailingSeps(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
full string
|
|
||||||
rem string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
full: "foo",
|
|
||||||
rem: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "",
|
|
||||||
rem: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "foo/",
|
|
||||||
rem: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "foo///bar///",
|
|
||||||
rem: "foo///bar",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/////",
|
|
||||||
rem: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/",
|
|
||||||
rem: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, p := range cases {
|
|
||||||
dir := StripTrailingSeps(p.full)
|
|
||||||
if dir != p.rem {
|
|
||||||
t.Fatalf(
|
|
||||||
"in '%s', got dir='%s' (expected '%s')",
|
|
||||||
p.full, dir, p.rem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStripLeadingSeps(t *testing.T) {
|
|
||||||
cases := []struct {
|
|
||||||
full string
|
|
||||||
rem string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
full: "foo",
|
|
||||||
rem: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "",
|
|
||||||
rem: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/foo",
|
|
||||||
rem: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "///foo///bar///",
|
|
||||||
rem: "foo///bar///",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/////",
|
|
||||||
rem: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
full: "/",
|
|
||||||
rem: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, p := range cases {
|
|
||||||
dir := StripLeadingSeps(p.full)
|
|
||||||
if dir != p.rem {
|
|
||||||
t.Fatalf(
|
|
||||||
"in '%s', got dir='%s' (expected '%s')",
|
|
||||||
p.full, dir, p.rem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package annotations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type annoMap map[string]string
|
|
||||||
|
|
||||||
type Filter struct {
|
|
||||||
// Annotations is the set of annotations to apply to the inputs
|
|
||||||
Annotations annoMap `yaml:"annotations,omitempty"`
|
|
||||||
|
|
||||||
// FsSlice contains the FieldSpecs to locate the namespace field
|
|
||||||
FsSlice types.FsSlice
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ kio.Filter = Filter{}
|
|
||||||
|
|
||||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|
||||||
keys := yaml.SortedMapKeys(f.Annotations)
|
|
||||||
_, err := kio.FilterAll(yaml.FilterFunc(
|
|
||||||
func(node *yaml.RNode) (*yaml.RNode, error) {
|
|
||||||
for _, k := range keys {
|
|
||||||
if err := node.PipeE(fsslice.Filter{
|
|
||||||
FsSlice: f.FsSlice,
|
|
||||||
SetValue: filtersutil.SetEntry(
|
|
||||||
k, f.Annotations[k], yaml.NodeTagString),
|
|
||||||
CreateKind: yaml.MappingNode, // Annotations are MappingNodes.
|
|
||||||
CreateTag: yaml.NodeTagMap,
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return node, nil
|
|
||||||
})).Filter(nodes)
|
|
||||||
return nodes, err
|
|
||||||
}
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package annotations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
|
||||||
filtertest_test "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var annosFs = builtinconfig.MakeDefaultConfig().CommonAnnotations
|
|
||||||
|
|
||||||
func TestAnnotations_Filter(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input string
|
|
||||||
expectedOutput string
|
|
||||||
filter Filter
|
|
||||||
fsslice types.FsSlice
|
|
||||||
}{
|
|
||||||
"add": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
annotations:
|
|
||||||
hero: batman
|
|
||||||
fiend: riddler
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
annotations:
|
|
||||||
hero: batman
|
|
||||||
fiend: riddler
|
|
||||||
auto: ford
|
|
||||||
bean: cannellini
|
|
||||||
clown: emmett kelley
|
|
||||||
dragon: smaug
|
|
||||||
`,
|
|
||||||
filter: Filter{Annotations: annoMap{
|
|
||||||
"clown": "emmett kelley",
|
|
||||||
"auto": "ford",
|
|
||||||
"dragon": "smaug",
|
|
||||||
"bean": "cannellini",
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
"update": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
annotations:
|
|
||||||
hero: batman
|
|
||||||
fiend: riddler
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
annotations:
|
|
||||||
hero: superman
|
|
||||||
fiend: luthor
|
|
||||||
bean: cannellini
|
|
||||||
clown: emmett kelley
|
|
||||||
`,
|
|
||||||
filter: Filter{Annotations: annoMap{
|
|
||||||
"clown": "emmett kelley",
|
|
||||||
"hero": "superman",
|
|
||||||
"fiend": "luthor",
|
|
||||||
"bean": "cannellini",
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
"data-fieldspecs": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
annotations:
|
|
||||||
sleater: kinney
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
sleater: kinney
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
annotations:
|
|
||||||
sleater: kinney
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
sleater: kinney
|
|
||||||
`,
|
|
||||||
filter: Filter{Annotations: annoMap{
|
|
||||||
"sleater": "kinney",
|
|
||||||
}},
|
|
||||||
fsslice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "a/b",
|
|
||||||
CreateIfNotPresent: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"number": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
annotations:
|
|
||||||
hero: batman
|
|
||||||
fiend: riddler
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
annotations:
|
|
||||||
hero: batman
|
|
||||||
fiend: riddler
|
|
||||||
2: ford
|
|
||||||
clown: "1"
|
|
||||||
`,
|
|
||||||
filter: Filter{Annotations: annoMap{
|
|
||||||
"clown": "1",
|
|
||||||
"2": "ford",
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
|
|
||||||
// test quoting of values which are not considered strings in yaml 1.1
|
|
||||||
"yaml_1_1_compatibility": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
annotations:
|
|
||||||
hero: batman
|
|
||||||
fiend: riddler
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
annotations:
|
|
||||||
hero: batman
|
|
||||||
fiend: riddler
|
|
||||||
a: "y"
|
|
||||||
b: y1
|
|
||||||
c: "yes"
|
|
||||||
d: yes1
|
|
||||||
e: "true"
|
|
||||||
f: true1
|
|
||||||
`,
|
|
||||||
filter: Filter{Annotations: annoMap{
|
|
||||||
"a": "y",
|
|
||||||
"b": "y1",
|
|
||||||
"c": "yes",
|
|
||||||
"d": "yes1",
|
|
||||||
"e": "true",
|
|
||||||
"f": "true1",
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
|
|
||||||
// test quoting of values which are not considered strings in yaml 1.1
|
|
||||||
"null_annotations": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
annotations: null
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
annotations:
|
|
||||||
a: a1
|
|
||||||
b: b1
|
|
||||||
`,
|
|
||||||
filter: Filter{Annotations: annoMap{
|
|
||||||
"a": "a1",
|
|
||||||
"b": "b1",
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
|
||||||
t.Run(tn, func(t *testing.T) {
|
|
||||||
filter := tc.filter
|
|
||||||
filter.FsSlice = append(annosFs, tc.fsslice...)
|
|
||||||
if !assert.Equal(t,
|
|
||||||
strings.TrimSpace(tc.expectedOutput),
|
|
||||||
strings.TrimSpace(filtertest_test.RunFilter(t, tc.input, filter))) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// Package annotations contains a kio.Filter implementation of the kustomize
|
|
||||||
// annotations transformer.
|
|
||||||
package annotations
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package annotations
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/internal/plugins/builtinconfig"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleFilter() {
|
|
||||||
fss := builtinconfig.MakeDefaultConfig().CommonAnnotations
|
|
||||||
err := kio.Pipeline{
|
|
||||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
`)}},
|
|
||||||
Filters: []kio.Filter{Filter{
|
|
||||||
Annotations: map[string]string{
|
|
||||||
"foo": "bar",
|
|
||||||
"booleanValue": "true",
|
|
||||||
"numberValue": "42",
|
|
||||||
},
|
|
||||||
FsSlice: fss,
|
|
||||||
}},
|
|
||||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
|
||||||
}.Execute()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Foo
|
|
||||||
// metadata:
|
|
||||||
// name: instance
|
|
||||||
// annotations:
|
|
||||||
// booleanValue: "true"
|
|
||||||
// foo: bar
|
|
||||||
// numberValue: "42"
|
|
||||||
// ---
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Bar
|
|
||||||
// metadata:
|
|
||||||
// name: instance
|
|
||||||
// annotations:
|
|
||||||
// booleanValue: "true"
|
|
||||||
// foo: bar
|
|
||||||
// numberValue: "42"
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
package filters
|
|
||||||
|
|
||||||
// Package filters collects various implementations
|
|
||||||
// sigs.k8s.io/kustomize/kyaml/kio.Filter used by kustomize
|
|
||||||
// transformers to modify kubernetes objects.
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// Package fieldspec contains a yaml.Filter to modify a resource
|
|
||||||
// that matches the FieldSpec.
|
|
||||||
package fieldspec
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package fieldspec_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
. "sigs.k8s.io/kustomize/api/filters/fieldspec"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleFilter() {
|
|
||||||
in := &kio.ByteReader{
|
|
||||||
Reader: bytes.NewBufferString(`
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
`),
|
|
||||||
}
|
|
||||||
fltr := Filter{
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
SetValue: filtersutil.SetScalar("green"),
|
|
||||||
FieldSpec: types.FieldSpec{Path: "a/b", CreateIfNotPresent: true},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := kio.Pipeline{
|
|
||||||
Inputs: []kio.Reader{in},
|
|
||||||
Filters: []kio.Filter{kio.FilterAll(fltr)},
|
|
||||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
|
||||||
}.Execute()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Foo
|
|
||||||
// metadata:
|
|
||||||
// name: instance
|
|
||||||
// a:
|
|
||||||
// b: green
|
|
||||||
// ---
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Bar
|
|
||||||
// metadata:
|
|
||||||
// name: instance
|
|
||||||
// a:
|
|
||||||
// b: green
|
|
||||||
}
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package fieldspec
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
|
||||||
"sigs.k8s.io/kustomize/api/internal/utils"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/errors"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/resid"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ yaml.Filter = Filter{}
|
|
||||||
|
|
||||||
// Filter possibly mutates its object argument using a FieldSpec.
|
|
||||||
// If the object matches the FieldSpec, and the node found
|
|
||||||
// by following the fieldSpec's path is non-null, this filter calls
|
|
||||||
// the setValue function on the node at the end of the path.
|
|
||||||
// If any part of the path doesn't exist, the filter returns
|
|
||||||
// without doing anything and without error, unless it was set
|
|
||||||
// to create the path. If set to create, it creates a tree of maps
|
|
||||||
// along the path, and the leaf node gets the setValue called on it.
|
|
||||||
// Error on GVK mismatch, empty or poorly formed path.
|
|
||||||
// Filter expect kustomize style paths, not JSON paths.
|
|
||||||
// Filter stores internal state and should not be reused
|
|
||||||
type Filter struct {
|
|
||||||
// FieldSpec contains the path to the value to set.
|
|
||||||
FieldSpec types.FieldSpec `yaml:"fieldSpec"`
|
|
||||||
|
|
||||||
// Set the field using this function
|
|
||||||
SetValue filtersutil.SetFn
|
|
||||||
|
|
||||||
// CreateKind defines the type of node to create if the field is not found
|
|
||||||
CreateKind yaml.Kind
|
|
||||||
|
|
||||||
CreateTag string
|
|
||||||
|
|
||||||
// path keeps internal state about the current path
|
|
||||||
path []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
|
||||||
// check if the FieldSpec applies to the object
|
|
||||||
if match := isMatchGVK(fltr.FieldSpec, obj); !match {
|
|
||||||
return obj, nil
|
|
||||||
}
|
|
||||||
fltr.path = utils.PathSplitter(fltr.FieldSpec.Path, "/")
|
|
||||||
if err := fltr.filter(obj); err != nil {
|
|
||||||
s, _ := obj.String()
|
|
||||||
return nil, errors.WrapPrefixf(err,
|
|
||||||
"considering field '%s' of object\n%v", fltr.FieldSpec.Path, s)
|
|
||||||
}
|
|
||||||
return obj, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Recursively called.
|
|
||||||
func (fltr Filter) filter(obj *yaml.RNode) error {
|
|
||||||
if len(fltr.path) == 0 {
|
|
||||||
// found the field -- set its value
|
|
||||||
return fltr.SetValue(obj)
|
|
||||||
}
|
|
||||||
if obj.IsTaggedNull() || obj.IsNil() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch obj.YNode().Kind {
|
|
||||||
case yaml.SequenceNode:
|
|
||||||
return fltr.handleSequence(obj)
|
|
||||||
case yaml.MappingNode:
|
|
||||||
return fltr.handleMap(obj)
|
|
||||||
case yaml.AliasNode:
|
|
||||||
return fltr.filter(yaml.NewRNode(obj.YNode().Alias))
|
|
||||||
default:
|
|
||||||
return errors.Errorf("expected sequence or mapping node")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleMap calls filter on the map field matching the next path element
|
|
||||||
func (fltr Filter) handleMap(obj *yaml.RNode) error {
|
|
||||||
fieldName, isSeq := isSequenceField(fltr.path[0])
|
|
||||||
if fieldName == "" {
|
|
||||||
return fmt.Errorf("cannot set or create an empty field name")
|
|
||||||
}
|
|
||||||
// lookup the field matching the next path element
|
|
||||||
var operation yaml.Filter
|
|
||||||
var kind yaml.Kind
|
|
||||||
tag := yaml.NodeTagEmpty
|
|
||||||
switch {
|
|
||||||
case !fltr.FieldSpec.CreateIfNotPresent || fltr.CreateKind == 0 || isSeq:
|
|
||||||
// don't create the field if we don't find it
|
|
||||||
operation = yaml.Lookup(fieldName)
|
|
||||||
if isSeq {
|
|
||||||
// The query path thinks this field should be a sequence;
|
|
||||||
// accept this hint for use later if the tag is NodeTagNull.
|
|
||||||
kind = yaml.SequenceNode
|
|
||||||
}
|
|
||||||
case len(fltr.path) <= 1:
|
|
||||||
// create the field if it is missing: use the provided node kind
|
|
||||||
operation = yaml.LookupCreate(fltr.CreateKind, fieldName)
|
|
||||||
kind = fltr.CreateKind
|
|
||||||
tag = fltr.CreateTag
|
|
||||||
default:
|
|
||||||
// create the field if it is missing: must be a mapping node
|
|
||||||
operation = yaml.LookupCreate(yaml.MappingNode, fieldName)
|
|
||||||
kind = yaml.MappingNode
|
|
||||||
tag = yaml.NodeTagMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// locate (or maybe create) the field
|
|
||||||
field, err := obj.Pipe(operation)
|
|
||||||
if err != nil {
|
|
||||||
return errors.WrapPrefixf(err, "fieldName: %s", fieldName)
|
|
||||||
}
|
|
||||||
if field == nil {
|
|
||||||
// No error if field not found.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the value exists, but is null and kind is set,
|
|
||||||
// then change it to the creation type
|
|
||||||
// TODO: update yaml.LookupCreate to support this
|
|
||||||
if field.YNode().Tag == yaml.NodeTagNull && yaml.IsCreate(kind) {
|
|
||||||
field.YNode().Kind = kind
|
|
||||||
field.YNode().Tag = tag
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy the current fltr and change the path on the copy
|
|
||||||
var next = fltr
|
|
||||||
// call filter for the next path element on the matching field
|
|
||||||
next.path = fltr.path[1:]
|
|
||||||
return next.filter(field)
|
|
||||||
}
|
|
||||||
|
|
||||||
// seq calls filter on all sequence elements
|
|
||||||
func (fltr Filter) handleSequence(obj *yaml.RNode) error {
|
|
||||||
if err := obj.VisitElements(func(node *yaml.RNode) error {
|
|
||||||
// recurse on each element -- re-allocating a Filter is
|
|
||||||
// not strictly required, but is more consistent with field
|
|
||||||
// and less likely to have side effects
|
|
||||||
// keep the entire path -- it does not contain parts for sequences
|
|
||||||
return fltr.filter(node)
|
|
||||||
}); err != nil {
|
|
||||||
return errors.WrapPrefixf(err,
|
|
||||||
"visit traversal on path: %v", fltr.path)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isSequenceField returns true if the path element is for a sequence field.
|
|
||||||
// isSequence also returns the path element with the '[]' suffix trimmed
|
|
||||||
func isSequenceField(name string) (string, bool) {
|
|
||||||
shorter := strings.TrimSuffix(name, "[]")
|
|
||||||
return shorter, shorter != name
|
|
||||||
}
|
|
||||||
|
|
||||||
// isMatchGVK returns true if the fs.GVK matches the obj GVK.
|
|
||||||
func isMatchGVK(fs types.FieldSpec, obj *yaml.RNode) bool {
|
|
||||||
if kind := obj.GetKind(); fs.Kind != "" && fs.Kind != kind {
|
|
||||||
// kind doesn't match
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse the group and version from the apiVersion field
|
|
||||||
group, version := resid.ParseGroupVersion(obj.GetApiVersion())
|
|
||||||
|
|
||||||
if fs.Group != "" && fs.Group != group {
|
|
||||||
// group doesn't match
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if fs.Version != "" && fs.Version != version {
|
|
||||||
// version doesn't match
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
@@ -1,568 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package fieldspec_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFilter_Filter(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input string
|
|
||||||
expected string
|
|
||||||
filter fieldspec.Filter
|
|
||||||
fieldSpec string
|
|
||||||
error string
|
|
||||||
}{
|
|
||||||
"path not found": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
|
||||||
group: foo
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo
|
|
||||||
kind: Bar
|
|
||||||
xxx:
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo
|
|
||||||
kind: Bar
|
|
||||||
xxx:
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"empty path": {
|
|
||||||
fieldSpec: `
|
|
||||||
group: foo
|
|
||||||
version: v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1
|
|
||||||
kind: Bar
|
|
||||||
xxx:
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo
|
|
||||||
kind: Bar
|
|
||||||
xxx:
|
|
||||||
`,
|
|
||||||
error: `considering field '' of object
|
|
||||||
apiVersion: foo/v1
|
|
||||||
kind: Bar
|
|
||||||
xxx:
|
|
||||||
: cannot set or create an empty field name`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"update": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
|
||||||
group: foo
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: e
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"update-kind-not-match": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
|
||||||
group: foo
|
|
||||||
kind: Bar1
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar2
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar2
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"update-group-not-match": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
|
||||||
group: foo1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo2/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo2/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"update-version-not-match": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
|
||||||
group: foo
|
|
||||||
version: v1beta1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta2
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta2
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"bad-version": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
|
||||||
group: foo
|
|
||||||
version: v1beta1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta2/something
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta2/something
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"bad-meta": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
|
||||||
group: foo
|
|
||||||
version: v1beta1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
a:
|
|
||||||
b: c
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"miss-match-type": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b/c
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: a
|
|
||||||
`,
|
|
||||||
error: `considering field 'a/b/c' of object
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: a
|
|
||||||
: expected sequence or mapping node`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"add": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b/c/d
|
|
||||||
group: foo
|
|
||||||
create: true
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a: {}
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a: {b: {c: {d: e}}}
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"update-in-sequence": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b[]/c/d
|
|
||||||
group: foo
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
- c:
|
|
||||||
d: a
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
- c:
|
|
||||||
d: e
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Don't create a sequence
|
|
||||||
"empty-sequence-no-create": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b[]/c/d
|
|
||||||
group: foo
|
|
||||||
create: true
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a: {}
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a: {}
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Create a new field for an element in a sequence
|
|
||||||
"empty-sequence-create": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b[]/c/d
|
|
||||||
group: foo
|
|
||||||
create: true
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
- c: {}
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1beta1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b:
|
|
||||||
- c: {d: e}
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"group v1": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
|
||||||
group: v1
|
|
||||||
create: true
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"version v1": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b
|
|
||||||
version: v1
|
|
||||||
create: true
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b: e
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"successfully set field on array entry no sequence hint": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: spec/containers/image
|
|
||||||
version: v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: foo
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: bar
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("bar"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"successfully set field on array entry with sequence hint": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: spec/containers[]/image
|
|
||||||
version: v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: foo
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: bar
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("bar"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"failure to set field on array entry with sequence hint in path": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: spec/containers[]/image
|
|
||||||
version: v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
spec:
|
|
||||||
containers: []
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("bar"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"failure to set field on array entry, no sequence hint in path": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: spec/containers/image
|
|
||||||
version: v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("bar"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"fieldname with slash '/'": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b\/c/d
|
|
||||||
version: v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b/c:
|
|
||||||
d: foo
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b/c:
|
|
||||||
d: bar
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("bar"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"fieldname with multiple '/'": {
|
|
||||||
fieldSpec: `
|
|
||||||
path: a/b\/c/d\/e/f
|
|
||||||
version: v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b/c:
|
|
||||||
d/e:
|
|
||||||
f: foo
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Bar
|
|
||||||
a:
|
|
||||||
b/c:
|
|
||||||
d/e:
|
|
||||||
f: bar
|
|
||||||
`,
|
|
||||||
filter: fieldspec.Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("bar"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for n := range testCases {
|
|
||||||
tc := testCases[n]
|
|
||||||
t.Run(n, func(t *testing.T) {
|
|
||||||
err := yaml.Unmarshal([]byte(tc.fieldSpec), &tc.filter.FieldSpec)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
|
||||||
rw := &kio.ByteReadWriter{
|
|
||||||
Reader: bytes.NewBufferString(tc.input),
|
|
||||||
Writer: out,
|
|
||||||
OmitReaderAnnotations: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the filter
|
|
||||||
err = kio.Pipeline{
|
|
||||||
Inputs: []kio.Reader{rw},
|
|
||||||
Filters: []kio.Filter{kio.FilterAll(tc.filter)},
|
|
||||||
Outputs: []kio.Writer{rw},
|
|
||||||
}.Execute()
|
|
||||||
if tc.error != "" {
|
|
||||||
if !assert.EqualError(t, err, tc.error) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
// stop rest of test
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
// check results
|
|
||||||
if !assert.Equal(t,
|
|
||||||
strings.TrimSpace(tc.expected),
|
|
||||||
strings.TrimSpace(out.String())) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package filtersutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SetFn is a function that accepts an RNode to possibly modify.
|
|
||||||
type SetFn func(*yaml.RNode) error
|
|
||||||
|
|
||||||
// SetScalar returns a SetFn to set a scalar value
|
|
||||||
func SetScalar(value string) SetFn {
|
|
||||||
return func(node *yaml.RNode) error {
|
|
||||||
return node.PipeE(yaml.FieldSetter{StringValue: value})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEntry returns a SetFn to set an entry in a map
|
|
||||||
func SetEntry(key, value, tag string) SetFn {
|
|
||||||
n := &yaml.Node{
|
|
||||||
Kind: yaml.ScalarNode,
|
|
||||||
Value: value,
|
|
||||||
Tag: tag,
|
|
||||||
}
|
|
||||||
if tag == yaml.NodeTagString && yaml.IsYaml1_1NonString(n) {
|
|
||||||
n.Style = yaml.DoubleQuotedStyle
|
|
||||||
}
|
|
||||||
return func(node *yaml.RNode) error {
|
|
||||||
return node.PipeE(yaml.FieldSetter{
|
|
||||||
Name: key,
|
|
||||||
Value: yaml.NewRNode(n),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// Package fsslice contains a yaml.Filter to modify a resource if
|
|
||||||
// it matches one or more FieldSpec entries.
|
|
||||||
package fsslice
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package fsslice_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleFilter() {
|
|
||||||
in := &kio.ByteReader{
|
|
||||||
Reader: bytes.NewBufferString(`
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
`),
|
|
||||||
}
|
|
||||||
fltr := fsslice.Filter{
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
SetValue: filtersutil.SetScalar("green"),
|
|
||||||
FsSlice: []types.FieldSpec{
|
|
||||||
{Path: "a/b", CreateIfNotPresent: true},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := kio.Pipeline{
|
|
||||||
Inputs: []kio.Reader{in},
|
|
||||||
Filters: []kio.Filter{kio.FilterAll(fltr)},
|
|
||||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
|
||||||
}.Execute()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Foo
|
|
||||||
// metadata:
|
|
||||||
// name: instance
|
|
||||||
// a:
|
|
||||||
// b: green
|
|
||||||
// ---
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Bar
|
|
||||||
// metadata:
|
|
||||||
// name: instance
|
|
||||||
// a:
|
|
||||||
// b: green
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package fsslice
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/fieldspec"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ yaml.Filter = Filter{}
|
|
||||||
|
|
||||||
// Filter ranges over an FsSlice to modify fields on a single object.
|
|
||||||
// An FsSlice is a range of FieldSpecs. A FieldSpec is a GVK plus a path.
|
|
||||||
type Filter struct {
|
|
||||||
// FieldSpecList list of FieldSpecs to set
|
|
||||||
FsSlice types.FsSlice `yaml:"fsSlice"`
|
|
||||||
|
|
||||||
// SetValue is called on each field that matches one of the FieldSpecs
|
|
||||||
SetValue filtersutil.SetFn
|
|
||||||
|
|
||||||
// CreateKind is used to create fields that do not exist
|
|
||||||
CreateKind yaml.Kind
|
|
||||||
|
|
||||||
// CreateTag is used to set the tag if encountering a null field
|
|
||||||
CreateTag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fltr Filter) Filter(obj *yaml.RNode) (*yaml.RNode, error) {
|
|
||||||
for i := range fltr.FsSlice {
|
|
||||||
// apply this FieldSpec
|
|
||||||
// create a new filter for each iteration because they
|
|
||||||
// store internal state about the field paths
|
|
||||||
_, err := (&fieldspec.Filter{
|
|
||||||
FieldSpec: fltr.FsSlice[i],
|
|
||||||
SetValue: fltr.SetValue,
|
|
||||||
CreateKind: fltr.CreateKind,
|
|
||||||
CreateTag: fltr.CreateTag,
|
|
||||||
}).Filter(obj)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj, nil
|
|
||||||
}
|
|
||||||
@@ -1,121 +0,0 @@
|
|||||||
// Copyright 2019 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package fsslice_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
|
||||||
. "sigs.k8s.io/kustomize/api/filters/fsslice"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TestCase struct {
|
|
||||||
input string
|
|
||||||
expected string
|
|
||||||
filter Filter
|
|
||||||
fsSlice string
|
|
||||||
error string
|
|
||||||
}
|
|
||||||
|
|
||||||
var tests = map[string]TestCase{
|
|
||||||
"empty": {
|
|
||||||
fsSlice: `
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"two": {
|
|
||||||
fsSlice: `
|
|
||||||
- path: a/b
|
|
||||||
group: foo
|
|
||||||
version: v1
|
|
||||||
create: true
|
|
||||||
kind: Bar
|
|
||||||
- path: q/r[]/s/t
|
|
||||||
group: foo
|
|
||||||
version: v1
|
|
||||||
create: true
|
|
||||||
kind: Bar
|
|
||||||
`,
|
|
||||||
input: `
|
|
||||||
apiVersion: foo/v1
|
|
||||||
kind: Bar
|
|
||||||
q:
|
|
||||||
r:
|
|
||||||
- s: {}
|
|
||||||
`,
|
|
||||||
expected: `
|
|
||||||
apiVersion: foo/v1
|
|
||||||
kind: Bar
|
|
||||||
q:
|
|
||||||
r:
|
|
||||||
- s: {t: e}
|
|
||||||
a:
|
|
||||||
b: e
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
SetValue: filtersutil.SetScalar("e"),
|
|
||||||
CreateKind: yaml.ScalarNode,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilter(t *testing.T) {
|
|
||||||
for name := range tests {
|
|
||||||
test := tests[name]
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
err := yaml.Unmarshal([]byte(test.fsSlice), &test.filter.FsSlice)
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
out := &bytes.Buffer{}
|
|
||||||
rw := &kio.ByteReadWriter{
|
|
||||||
Reader: bytes.NewBufferString(test.input),
|
|
||||||
Writer: out,
|
|
||||||
OmitReaderAnnotations: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the filter
|
|
||||||
err = kio.Pipeline{
|
|
||||||
Inputs: []kio.Reader{rw},
|
|
||||||
Filters: []kio.Filter{kio.FilterAll(test.filter)},
|
|
||||||
Outputs: []kio.Writer{rw},
|
|
||||||
}.Execute()
|
|
||||||
if test.error != "" {
|
|
||||||
if !assert.EqualError(t, err, test.error) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
// stop rest of test
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !assert.NoError(t, err) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
|
|
||||||
// check results
|
|
||||||
if !assert.Equal(t,
|
|
||||||
strings.TrimSpace(test.expected),
|
|
||||||
strings.TrimSpace(out.String())) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
// Package gkesagenerator contains a kio.Filter that that generates a
|
|
||||||
// iampolicy-related resources for a given cloud provider
|
|
||||||
package iampolicygenerator
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
// Copyright 2021 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package iampolicygenerator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleFilter() {
|
|
||||||
f := Filter{}
|
|
||||||
var err = yaml.Unmarshal([]byte(`
|
|
||||||
cloud: gke
|
|
||||||
kubernetesService:
|
|
||||||
namespace: k8s-namespace
|
|
||||||
name: k8s-sa-name
|
|
||||||
serviceAccount:
|
|
||||||
name: gsa-name
|
|
||||||
projectId: project-id
|
|
||||||
`), &f)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = kio.Pipeline{
|
|
||||||
Inputs: []kio.Reader{},
|
|
||||||
Filters: []kio.Filter{f},
|
|
||||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
|
||||||
}.Execute()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// apiVersion: v1
|
|
||||||
// kind: ServiceAccount
|
|
||||||
// metadata:
|
|
||||||
// annotations:
|
|
||||||
// iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
|
|
||||||
// name: k8s-sa-name
|
|
||||||
// namespace: k8s-namespace
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
// Copyright 2021 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package iampolicygenerator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Filter struct {
|
|
||||||
IAMPolicyGenerator types.IAMPolicyGeneratorArgs `json:",inline,omitempty" yaml:",inline,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter adds a GKE service account object to nodes
|
|
||||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|
||||||
switch f.IAMPolicyGenerator.Cloud {
|
|
||||||
case types.GKE:
|
|
||||||
IAMPolicyResources, err := f.generateGkeIAMPolicyResources()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
nodes = append(nodes, IAMPolicyResources...)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("cloud provider %s not supported yet", f.IAMPolicyGenerator.Cloud)
|
|
||||||
}
|
|
||||||
return nodes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Filter) generateGkeIAMPolicyResources() ([]*yaml.RNode, error) {
|
|
||||||
var result []*yaml.RNode
|
|
||||||
input := fmt.Sprintf(`
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
iam.gke.io/gcp-service-account: %s@%s.iam.gserviceaccount.com
|
|
||||||
name: %s
|
|
||||||
`, f.IAMPolicyGenerator.ServiceAccount.Name,
|
|
||||||
f.IAMPolicyGenerator.ProjectId,
|
|
||||||
f.IAMPolicyGenerator.KubernetesService.Name)
|
|
||||||
|
|
||||||
if f.IAMPolicyGenerator.Namespace != "" {
|
|
||||||
input = input + fmt.Sprintf("\n namespace: %s", f.IAMPolicyGenerator.Namespace)
|
|
||||||
}
|
|
||||||
|
|
||||||
sa, err := yaml.Parse(input)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(result, sa), nil
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
// Copyright 2021 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package iampolicygenerator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFilter(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
args types.IAMPolicyGeneratorArgs
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
"with namespace": {
|
|
||||||
args: types.IAMPolicyGeneratorArgs{
|
|
||||||
Cloud: types.GKE,
|
|
||||||
KubernetesService: types.KubernetesService{
|
|
||||||
Namespace: "k8s-namespace",
|
|
||||||
Name: "k8s-sa-name",
|
|
||||||
},
|
|
||||||
ServiceAccount: types.ServiceAccount{
|
|
||||||
Name: "gsa-name",
|
|
||||||
ProjectId: "project-id",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
|
|
||||||
name: k8s-sa-name
|
|
||||||
namespace: k8s-namespace
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
"without namespace": {
|
|
||||||
args: types.IAMPolicyGeneratorArgs{
|
|
||||||
Cloud: types.GKE,
|
|
||||||
KubernetesService: types.KubernetesService{
|
|
||||||
Name: "k8s-sa-name",
|
|
||||||
},
|
|
||||||
ServiceAccount: types.ServiceAccount{
|
|
||||||
Name: "gsa-name",
|
|
||||||
ProjectId: "project-id",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expected: `
|
|
||||||
apiVersion: v1
|
|
||||||
kind: ServiceAccount
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
iam.gke.io/gcp-service-account: gsa-name@project-id.iam.gserviceaccount.com
|
|
||||||
name: k8s-sa-name
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
|
||||||
t.Run(tn, func(t *testing.T) {
|
|
||||||
f := Filter{
|
|
||||||
IAMPolicyGenerator: tc.args,
|
|
||||||
}
|
|
||||||
actual := filtertest.RunFilter(t, "", f)
|
|
||||||
if !assert.Equal(t, strings.TrimSpace(tc.expected), strings.TrimSpace(actual)) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
// Package imagetag contains two kio.Filter implementations to cover the
|
|
||||||
// functionality of the kustomize imagetag transformer.
|
|
||||||
//
|
|
||||||
// Filter updates fields based on a FieldSpec and an ImageTag.
|
|
||||||
//
|
|
||||||
// LegacyFilter doesn't use a FieldSpec, and instead only updates image
|
|
||||||
// references if the field is name image and it is underneath a field called
|
|
||||||
// either containers or initContainers.
|
|
||||||
package imagetag
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package imagetag
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleFilter() {
|
|
||||||
err := kio.Pipeline{
|
|
||||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: FooBar
|
|
||||||
image: nginx
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: BarFoo
|
|
||||||
image: nginx:1.2.1
|
|
||||||
`)}},
|
|
||||||
Filters: []kio.Filter{Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
NewName: "apache",
|
|
||||||
Digest: "12345",
|
|
||||||
},
|
|
||||||
FsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/containers[]/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
|
||||||
}.Execute()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Foo
|
|
||||||
// metadata:
|
|
||||||
// name: instance
|
|
||||||
// spec:
|
|
||||||
// containers:
|
|
||||||
// - name: FooBar
|
|
||||||
// image: apache@12345
|
|
||||||
// ---
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Bar
|
|
||||||
// metadata:
|
|
||||||
// name: instance
|
|
||||||
// spec:
|
|
||||||
// containers:
|
|
||||||
// - name: BarFoo
|
|
||||||
// image: apache@12345
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleLegacyFilter() {
|
|
||||||
err := kio.Pipeline{
|
|
||||||
Inputs: []kio.Reader{&kio.ByteReader{Reader: bytes.NewBufferString(`
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: FooBar
|
|
||||||
image: nginx
|
|
||||||
---
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Bar
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: BarFoo
|
|
||||||
image: nginx:1.2.1
|
|
||||||
`)}},
|
|
||||||
Filters: []kio.Filter{LegacyFilter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
NewName: "apache",
|
|
||||||
Digest: "12345",
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
Outputs: []kio.Writer{kio.ByteWriter{Writer: os.Stdout}},
|
|
||||||
}.Execute()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Foo
|
|
||||||
// metadata:
|
|
||||||
// name: instance
|
|
||||||
// spec:
|
|
||||||
// containers:
|
|
||||||
// - name: FooBar
|
|
||||||
// image: apache@12345
|
|
||||||
// ---
|
|
||||||
// apiVersion: example.com/v1
|
|
||||||
// kind: Bar
|
|
||||||
// metadata:
|
|
||||||
// name: instance
|
|
||||||
// spec:
|
|
||||||
// containers:
|
|
||||||
// - name: BarFoo
|
|
||||||
// image: apache@12345
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package imagetag
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/filtersutil"
|
|
||||||
"sigs.k8s.io/kustomize/api/filters/fsslice"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Filter modifies an "image tag", the value used to specify the
|
|
||||||
// name, tag, version digest etc. of (docker) container images
|
|
||||||
// used by a pod template.
|
|
||||||
type Filter struct {
|
|
||||||
// imageTag is the tag we want to apply to the inputs
|
|
||||||
// The name of the image is used as a key, and other fields
|
|
||||||
// can specify a new name, tag, etc.
|
|
||||||
ImageTag types.Image `json:"imageTag,omitempty" yaml:"imageTag,omitempty"`
|
|
||||||
|
|
||||||
// FsSlice contains the FieldSpecs to locate an image field,
|
|
||||||
// e.g. Path: "spec/myContainers[]/image"
|
|
||||||
FsSlice types.FsSlice `json:"fieldSpecs,omitempty" yaml:"fieldSpecs,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ kio.Filter = Filter{}
|
|
||||||
|
|
||||||
func (f Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
|
|
||||||
_, err := kio.FilterAll(yaml.FilterFunc(f.filter)).Filter(nodes)
|
|
||||||
return nodes, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Filter) filter(node *yaml.RNode) (*yaml.RNode, error) {
|
|
||||||
// FsSlice is an allowlist, not a denyList, so to deny
|
|
||||||
// something via configuration a new config mechanism is
|
|
||||||
// needed. Until then, hardcode it.
|
|
||||||
if f.isOnDenyList(node) {
|
|
||||||
return node, nil
|
|
||||||
}
|
|
||||||
if err := node.PipeE(fsslice.Filter{
|
|
||||||
FsSlice: f.FsSlice,
|
|
||||||
SetValue: updateImageTagFn(f.ImageTag),
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return node, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Filter) isOnDenyList(node *yaml.RNode) bool {
|
|
||||||
meta, err := node.GetMeta()
|
|
||||||
if err != nil {
|
|
||||||
// A missing 'meta' field will cause problems elsewhere;
|
|
||||||
// ignore it here to keep the signature simple.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// Ignore CRDs
|
|
||||||
// https://github.com/kubernetes-sigs/kustomize/issues/890
|
|
||||||
return meta.Kind == `CustomResourceDefinition`
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateImageTagFn(imageTag types.Image) filtersutil.SetFn {
|
|
||||||
return func(node *yaml.RNode) error {
|
|
||||||
return node.PipeE(imageTagUpdater{
|
|
||||||
ImageTag: imageTag,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,674 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package imagetag
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
filtertest "sigs.k8s.io/kustomize/api/testutils/filtertest"
|
|
||||||
"sigs.k8s.io/kustomize/api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestImageTagUpdater_Filter(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
input string
|
|
||||||
expectedOutput string
|
|
||||||
filter Filter
|
|
||||||
fsSlice types.FsSlice
|
|
||||||
}{
|
|
||||||
"ignore CustomResourceDefinition": {
|
|
||||||
input: `
|
|
||||||
apiVersion: apiextensions.k8s.io/v1
|
|
||||||
kind: CustomResourceDefinition
|
|
||||||
metadata:
|
|
||||||
name: whatever
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: whatever
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
apiVersion: apiextensions.k8s.io/v1
|
|
||||||
kind: CustomResourceDefinition
|
|
||||||
metadata:
|
|
||||||
name: whatever
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: whatever
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "whatever",
|
|
||||||
NewName: "theImageShouldNotChangeInACrd",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/containers/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"legacy multiple images in containers": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: nginx:1.2.1
|
|
||||||
- image: nginx:2.1.2
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: apache@12345
|
|
||||||
- image: apache@12345
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
NewName: "apache",
|
|
||||||
Digest: "12345",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/containers/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"legacy both containers and initContainers": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: nginx:1.2.1
|
|
||||||
- image: tomcat:1.2.3
|
|
||||||
initContainers:
|
|
||||||
- image: nginx:1.2.1
|
|
||||||
- image: apache:1.2.3
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: apache:3.2.1
|
|
||||||
- image: tomcat:1.2.3
|
|
||||||
initContainers:
|
|
||||||
- image: apache:3.2.1
|
|
||||||
- image: apache:1.2.3
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
NewName: "apache",
|
|
||||||
NewTag: "3.2.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/containers/image",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "spec/initContainers/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"legacy updates at multiple depths": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: nginx:1.2.1
|
|
||||||
- image: tomcat:1.2.3
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
initContainers:
|
|
||||||
- image: nginx:1.2.1
|
|
||||||
- image: apache:1.2.3
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: apache:3.2.1
|
|
||||||
- image: tomcat:1.2.3
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
initContainers:
|
|
||||||
- image: apache:3.2.1
|
|
||||||
- image: apache:1.2.3
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
NewName: "apache",
|
|
||||||
NewTag: "3.2.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/containers/image",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/initContainers/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"update with digest": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
image: nginx:1.2.1
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
image: apache@12345
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
NewName: "apache",
|
|
||||||
Digest: "12345",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"multiple matches in sequence": {
|
|
||||||
input: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: nginx:1.2.1
|
|
||||||
- image: not_nginx@54321
|
|
||||||
- image: nginx:1.2.1
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
apiVersion: example.com/v1
|
|
||||||
kind: Foo
|
|
||||||
metadata:
|
|
||||||
name: instance
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: apache:3.2.1
|
|
||||||
- image: not_nginx@54321
|
|
||||||
- image: apache:3.2.1
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
NewName: "apache",
|
|
||||||
NewTag: "3.2.1",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/containers/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
"new Tag": {
|
|
||||||
input: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: nginx:1.7.9
|
|
||||||
name: nginx-tagged
|
|
||||||
- image: nginx:latest
|
|
||||||
name: nginx-latest
|
|
||||||
- image: foobar:1
|
|
||||||
name: replaced-with-digest
|
|
||||||
- image: postgres:1.8.0
|
|
||||||
name: postgresdb
|
|
||||||
initContainers:
|
|
||||||
- image: nginx
|
|
||||||
name: nginx-notag
|
|
||||||
- image: nginx@sha256:111111111111111111
|
|
||||||
name: nginx-sha256
|
|
||||||
- image: alpine:1.8.0
|
|
||||||
name: init-alpine`,
|
|
||||||
expectedOutput: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: nginx:v2
|
|
||||||
name: nginx-tagged
|
|
||||||
- image: nginx:v2
|
|
||||||
name: nginx-latest
|
|
||||||
- image: foobar:1
|
|
||||||
name: replaced-with-digest
|
|
||||||
- image: postgres:1.8.0
|
|
||||||
name: postgresdb
|
|
||||||
initContainers:
|
|
||||||
- image: nginx:v2
|
|
||||||
name: nginx-notag
|
|
||||||
- image: nginx:v2
|
|
||||||
name: nginx-sha256
|
|
||||||
- image: alpine:1.8.0
|
|
||||||
name: init-alpine
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
NewTag: "v2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/containers[]/image",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/initContainers[]/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"newImage": {
|
|
||||||
input: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: nginx:1.7.9
|
|
||||||
name: nginx-tagged
|
|
||||||
- image: nginx:latest
|
|
||||||
name: nginx-latest
|
|
||||||
- image: foobar:1
|
|
||||||
name: replaced-with-digest
|
|
||||||
- image: postgres:1.8.0
|
|
||||||
name: postgresdb
|
|
||||||
initContainers:
|
|
||||||
- image: nginx
|
|
||||||
name: nginx-notag
|
|
||||||
- image: nginx@sha256:111111111111111111
|
|
||||||
name: nginx-sha256
|
|
||||||
- image: alpine:1.8.0
|
|
||||||
name: init-alpine
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: busybox:1.7.9
|
|
||||||
name: nginx-tagged
|
|
||||||
- image: busybox:latest
|
|
||||||
name: nginx-latest
|
|
||||||
- image: foobar:1
|
|
||||||
name: replaced-with-digest
|
|
||||||
- image: postgres:1.8.0
|
|
||||||
name: postgresdb
|
|
||||||
initContainers:
|
|
||||||
- image: busybox
|
|
||||||
name: nginx-notag
|
|
||||||
- image: busybox@sha256:111111111111111111
|
|
||||||
name: nginx-sha256
|
|
||||||
- image: alpine:1.8.0
|
|
||||||
name: init-alpine
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
NewName: "busybox",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/containers[]/image",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/initContainers[]/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"newImageAndTag": {
|
|
||||||
input: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: nginx:1.7.9
|
|
||||||
name: nginx-tagged
|
|
||||||
- image: nginx:latest
|
|
||||||
name: nginx-latest
|
|
||||||
- image: foobar:1
|
|
||||||
name: replaced-with-digest
|
|
||||||
- image: postgres:1.8.0
|
|
||||||
name: postgresdb
|
|
||||||
initContainers:
|
|
||||||
- image: nginx
|
|
||||||
name: nginx-notag
|
|
||||||
- image: nginx@sha256:111111111111111111
|
|
||||||
name: nginx-sha256
|
|
||||||
- image: alpine:1.8.0
|
|
||||||
name: init-alpine
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: busybox:v3
|
|
||||||
name: nginx-tagged
|
|
||||||
- image: busybox:v3
|
|
||||||
name: nginx-latest
|
|
||||||
- image: foobar:1
|
|
||||||
name: replaced-with-digest
|
|
||||||
- image: postgres:1.8.0
|
|
||||||
name: postgresdb
|
|
||||||
initContainers:
|
|
||||||
- image: busybox:v3
|
|
||||||
name: nginx-notag
|
|
||||||
- image: busybox:v3
|
|
||||||
name: nginx-sha256
|
|
||||||
- image: alpine:1.8.0
|
|
||||||
name: init-alpine
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
NewName: "busybox",
|
|
||||||
NewTag: "v3",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/containers[]/image",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/initContainers[]/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"newDigest": {
|
|
||||||
input: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: nginx:1.7.9
|
|
||||||
name: nginx-tagged
|
|
||||||
- image: nginx:latest
|
|
||||||
name: nginx-latest
|
|
||||||
- image: foobar:1
|
|
||||||
name: replaced-with-digest
|
|
||||||
- image: postgres:1.8.0
|
|
||||||
name: postgresdb
|
|
||||||
initContainers:
|
|
||||||
- image: nginx
|
|
||||||
name: nginx-notag
|
|
||||||
- image: nginx@sha256:111111111111111111
|
|
||||||
name: nginx-sha256
|
|
||||||
- image: alpine:1.8.0
|
|
||||||
name: init-alpine
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: nginx@sha256:222222222222222222
|
|
||||||
name: nginx-tagged
|
|
||||||
- image: nginx@sha256:222222222222222222
|
|
||||||
name: nginx-latest
|
|
||||||
- image: foobar:1
|
|
||||||
name: replaced-with-digest
|
|
||||||
- image: postgres:1.8.0
|
|
||||||
name: postgresdb
|
|
||||||
initContainers:
|
|
||||||
- image: nginx@sha256:222222222222222222
|
|
||||||
name: nginx-notag
|
|
||||||
- image: nginx@sha256:222222222222222222
|
|
||||||
name: nginx-sha256
|
|
||||||
- image: alpine:1.8.0
|
|
||||||
name: init-alpine
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
Digest: "sha256:222222222222222222",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/containers/image",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/initContainers/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"newImageAndDigest": {
|
|
||||||
input: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: nginx:1.7.9
|
|
||||||
name: nginx-tagged
|
|
||||||
- image: nginx:latest
|
|
||||||
name: nginx-latest
|
|
||||||
- image: foobar:1
|
|
||||||
name: replaced-with-digest
|
|
||||||
- image: postgres:1.8.0
|
|
||||||
name: postgresdb
|
|
||||||
initContainers:
|
|
||||||
- image: nginx
|
|
||||||
name: nginx-notag
|
|
||||||
- image: nginx@sha256:111111111111111111
|
|
||||||
name: nginx-sha256
|
|
||||||
- image: alpine:1.8.0
|
|
||||||
name: init-alpine
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: busybox@sha256:222222222222222222
|
|
||||||
name: nginx-tagged
|
|
||||||
- image: busybox@sha256:222222222222222222
|
|
||||||
name: nginx-latest
|
|
||||||
- image: foobar:1
|
|
||||||
name: replaced-with-digest
|
|
||||||
- image: postgres:1.8.0
|
|
||||||
name: postgresdb
|
|
||||||
initContainers:
|
|
||||||
- image: busybox@sha256:222222222222222222
|
|
||||||
name: nginx-notag
|
|
||||||
- image: busybox@sha256:222222222222222222
|
|
||||||
name: nginx-sha256
|
|
||||||
- image: alpine:1.8.0
|
|
||||||
name: init-alpine
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
NewName: "busybox",
|
|
||||||
Digest: "sha256:222222222222222222",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/containers[]/image",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/initContainers[]/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"emptyContainers": {
|
|
||||||
input: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
containers: []
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "nginx",
|
|
||||||
NewTag: "v2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/containers[]/image",
|
|
||||||
// CreateIfNotPresent: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"tagWithBraces": {
|
|
||||||
input: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: some.registry.io/my-image:{GENERATED_TAG}
|
|
||||||
name: my-image
|
|
||||||
`,
|
|
||||||
expectedOutput: `
|
|
||||||
group: apps
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: deploy1
|
|
||||||
spec:
|
|
||||||
template:
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- image: some.registry.io/my-image:my-fixed-tag
|
|
||||||
name: my-image
|
|
||||||
`,
|
|
||||||
filter: Filter{
|
|
||||||
ImageTag: types.Image{
|
|
||||||
Name: "some.registry.io/my-image",
|
|
||||||
NewTag: "my-fixed-tag",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fsSlice: []types.FieldSpec{
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/containers[]/image",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Path: "spec/template/spec/initContainers[]/image",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for tn, tc := range testCases {
|
|
||||||
t.Run(tn, func(t *testing.T) {
|
|
||||||
filter := tc.filter
|
|
||||||
filter.FsSlice = tc.fsSlice
|
|
||||||
if !assert.Equal(t,
|
|
||||||
strings.TrimSpace(tc.expectedOutput),
|
|
||||||
strings.TrimSpace(filtertest.RunFilter(t, tc.input, filter))) {
|
|
||||||
t.FailNow()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
// Copyright 2020 The Kubernetes Authors.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package imagetag
|
|
||||||
|
|
||||||
import (
|
|
||||||
"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 contains(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 contains(slice []string, str string) bool {
|
|
||||||
for _, s := range slice {
|
|
||||||
if s == str {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
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,181 +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 {
|
|
||||||
id := makeResId(n)
|
|
||||||
if id.IsSelectedBy(t.Select.ResId) && !rejectId(t.Reject, id) {
|
|
||||||
err := applyToNode(n, value, t)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
if makeResId(n).IsSelectedBy(selector.ResId) {
|
|
||||||
if len(matches) > 0 {
|
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"multiple matches for selector %s", selector)
|
|
||||||
}
|
|
||||||
matches = append(matches, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(matches) == 0 {
|
|
||||||
return nil, fmt.Errorf("nothing selected by %s", selector)
|
|
||||||
}
|
|
||||||
return matches[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeResId makes a ResId from an RNode.
|
|
||||||
func makeResId(n *yaml.RNode) *resid.ResId {
|
|
||||||
apiVersion := n.Field(yaml.APIVersionField)
|
|
||||||
var group, version string
|
|
||||||
if apiVersion != nil {
|
|
||||||
group, version = resid.ParseGroupVersion(yaml.GetValue(apiVersion.Value))
|
|
||||||
}
|
|
||||||
return &resid.ResId{
|
|
||||||
Gvk: resid.Gvk{Group: group, Version: version, Kind: n.GetKind()},
|
|
||||||
Name: n.GetName(),
|
|
||||||
Namespace: n.GetNamespace(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user