Compare commits

...

49 Commits

Author SHA1 Message Date
k8s-ci-robot
4569a09d54 Merge pull request #200 from monopole/deleteDuplicativeCode
Delete duplicative code.
2018-07-25 09:33:55 -07:00
jregan
25d3ad7522 Delete duplicative code. 2018-07-24 20:42:43 -07:00
Jeff Regan
77e18724db Merge pull request #199 from monopole/moarDeletion
Drop the notion of the SchemeLoader
2018-07-24 18:18:25 -07:00
k8s-ci-robot
12d1771bb3 Merge pull request #197 from Liujingfang1/master
Change configmapGenerator to configmap.yaml in helloWorld example
2018-07-24 13:50:56 -07:00
Jeffrey Regan
a78aa22399 Drop useless or duplicative code. 2018-07-24 13:25:26 -07:00
Jingfang Liu
05a91893bf break helloWorld example into two examples:
- one for declaring a ConfigMap as resources
- one for declaring a ConfigMap from ConfigMapGenerator and rolling
update
2018-07-24 11:35:15 -07:00
Jeff Regan
8d420ec3f7 Merge pull request #196 from Liujingfang1/cr
Add docs and demo for imageTags
2018-07-23 16:44:12 -07:00
Jingfang Liu
838a766d12 Add docs and demo for imageTags 2018-07-23 16:35:04 -07:00
k8s-ci-robot
50d79e4d3e Merge pull request #198 from monopole/anotherWayToDelete
Delete some code.
2018-07-23 15:52:24 -07:00
Jeff Regan
4d2d450f6e Merge pull request #191 from babiel/fix-diff-tests-on-macos
Fix wrong path in diff tests on macOS
2018-07-23 15:37:29 -07:00
Jeff Regan
fdc46fb0b1 Delete some code. 2018-07-23 15:23:30 -07:00
k8s-ci-robot
92ac9b5a0e Merge pull request #194 from droot/bugfix/version-fix-issue-148
fixed version info injection in build script
2018-07-23 14:42:18 -07:00
Jeff Regan
857a9df70f Merge pull request #195 from monopole/tightenUp
Pull factories up call stack (make them less often).
2018-07-23 14:02:47 -07:00
Jeffrey Regan
969f4f28fa Pull factories out of the bowels. 2018-07-23 13:48:46 -07:00
Sunil Arora
58aa45c50a fixed version info injection in build script
fixes #148
2018-07-23 11:54:37 -07:00
k8s-ci-robot
5715f4bab4 Merge pull request #192 from Liujingfang1/cr
Add set imagetag command
2018-07-23 10:56:18 -07:00
Jingfang Liu
c8502c78f5 drop complete function from setImageTag subcommand 2018-07-23 10:47:52 -07:00
Jingfang Liu
909de5c94a Add set imagetag command 2018-07-23 10:24:32 -07:00
Maximilian Gaß
2eaeb83ec3 Fix wrong path in diff tests on macOS 2018-07-23 17:00:41 +02:00
k8s-ci-robot
03b9c2a3a3 Merge pull request #188 from Liujingfang1/cr
Enable imageTagTransformer in application
2018-07-20 11:54:56 -07:00
Jingfang Liu
59b98727ec enable imageTagTransformer in application 2018-07-20 11:30:34 -07:00
Jingfang Liu
5851f96524 Add initContainers in imageTagTransformer 2018-07-20 11:30:06 -07:00
k8s-ci-robot
08be3f061e Merge pull request #187 from monopole/secFactory
Introduce secret factory.
2018-07-20 11:17:53 -07:00
k8s-ci-robot
5906aaba19 Merge pull request #184 from Liujingfang1/cr
Add imageTagTransformer
2018-07-20 10:54:07 -07:00
Jingfang Liu
4b6f180d0c address comments 2018-07-20 10:45:17 -07:00
Jeffrey Regan
7f22f187f8 Introduce secret factory. 2018-07-20 10:40:47 -07:00
Jingfang Liu
fa3a64e352 Add imageTagTransformer 2018-07-20 10:23:12 -07:00
Jeff Regan
82f2cf9124 Merge pull request #186 from monopole/secretFactory
Inject a file system object into "Application".
2018-07-20 09:24:19 -07:00
Jeffrey Regan
276693cf0e Make a secret factory. 2018-07-20 09:09:52 -07:00
Jeff Regan
0197c019cc Merge pull request #185 from monopole/evenMoreFix86
Start remerging two forked sets of configmap factory code
2018-07-19 18:44:20 -07:00
Jeffrey Regan
9576a81787 Put the two sets of configmap make codes sidebyside 2018-07-19 18:33:55 -07:00
k8s-ci-robot
ff4a1c0b4f Merge pull request #183 from monopole/moreFix86
Remove a util package; more cleanup for #86
2018-07-19 16:19:10 -07:00
Jeff Regan
7dd28b1fd9 Merge pull request #176 from babiel/do-not-create-networkpolicy-matchlabels
Disable NetworkPolicy podSelector.matchLabels CreateIfNotPresent
2018-07-19 15:51:13 -07:00
Jeffrey Regan
b754557418 Remove a util package; more cleanup for #86 2018-07-19 14:39:18 -07:00
k8s-ci-robot
f305c0d791 Merge pull request #182 from Liujingfang1/cr
Add ContainerRef in kustomization type
2018-07-19 14:34:41 -07:00
Jingfang Liu
3fdaa2e903 Add ImageTags in kustomization type 2018-07-19 14:29:29 -07:00
k8s-ci-robot
964c74fb46 Merge pull request #181 from monopole/fix86
configMap factory refactor for #86
2018-07-19 14:14:53 -07:00
Jeffrey Regan
f14988ff80 configMap factory refactor for #86 2018-07-19 14:06:51 -07:00
k8s-ci-robot
f1adbfdbff Merge pull request #180 from knqyf263/fix_docs
Fix configGeneration.md
2018-07-19 08:37:53 -07:00
knqyf263
072bf992b0 Fix configGeneration.md 2018-07-19 11:09:00 +09:00
Jeff Regan
2d0d09e178 Merge pull request #179 from monopole/nitfixes
Fix cluster of silly Go nits.
2018-07-18 17:49:28 -07:00
Jeffrey Regan
564b0d6827 Fix cluster of silly Go nits. 2018-07-18 17:45:17 -07:00
k8s-ci-robot
5edae84a9e Merge pull request #177 from monopole/improveFsAbstraction
Replace os.Stat with IsDir and Exists, simplifying FS abstraction.
2018-07-18 13:43:06 -07:00
Jeffrey Regan
9432671887 Replace os.Stat with IsDir, simplifying FS abstraction. 2018-07-18 12:57:53 -07:00
Jeff Regan
8fda0f87ab Merge pull request #159 from Liujingfang1/master
remove adding hash for configmap/secret read from resource yaml files
2018-07-18 11:10:09 -07:00
Jingfang Liu
08bc8637c8 set the default behavior for SecretGenerator and ConfigMapGenerator as create 2018-07-18 10:59:38 -07:00
Jingfang Liu
9645f397ef remove adding hash for configmap/secret read from resource yaml files 2018-07-18 10:57:50 -07:00
Maximilian Gaß
ed9f716361 Add unit test for NetworkPolicy 2018-07-18 14:11:18 +02:00
Maximilian Gaß
9986b65326 Disable creation of NetworkPolicy podSelector.matchLabels 2018-07-18 14:01:22 +02:00
81 changed files with 1812 additions and 1602 deletions

2
Gopkg.lock generated
View File

@@ -296,6 +296,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "586d4cb9094e9b5c0731f16e931b953e9c0f709b7125a7537ae3625ada179eee"
inputs-digest = "74d444cd05ac6f803960180ec8ccfd5a4358077f7c79a5218a243554cb599274"
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -46,7 +46,9 @@ function testGoMetalinter {
--enable=misspell \
--enable=structcheck \
--enable=deadcode \
--enable=goimports \
# Disabling 'goimports' because it reports hyphens in imported package \
# names as errors, and we have to vendor them in regardless. \
# --enable=goimports \
--enable=varcheck \
--enable=goconst \
--enable=unparam \

View File

@@ -56,4 +56,4 @@ case $key in
esac
done
/goreleaser release --config=build/goreleaser.yml --rm-dist --skip-validate ${SNAPSHOT}
/goreleaser release --config=build/goreleaser.yaml --rm-dist --skip-validate ${SNAPSHOT}

View File

@@ -4,7 +4,7 @@ project_name: kustomize
builds:
- main: ./kustomize.go
binary: kustomize
ldflags: -s -X github.com/kubernetes-sigs/kustomize/version.kustomizeVersion={{.Version}} -X github.com/kubernetes-sigs/kustomize/version.gitCommit={{.Commit}} -X github.com/kubernetes-sigs/kustomize/version.buildDate={{.Date}}
ldflags: -s -X github.com/kubernetes-sigs/kustomize/pkg/commands.kustomizeVersion={{.Version}} -X github.com/kubernetes-sigs/kustomize/pkg/commands.gitCommit={{.Commit}} -X github.com/kubernetes-sigs/kustomize/pkg/commands.buildDate={{.Date}}
goos:
- darwin
- linux

View File

@@ -1,33 +0,0 @@
# This is an example goreleaser.yaml file with some sane defaults.
# Make sure to check the documentation at http://goreleaser.com
project_name: kustomize
builds:
- main: ./kustomize.go
binary: kustomize
ldflags: -s -X github.com/kubernetes-sigs/kustomize/version.kustomizeVersion={{.Version}} -X github.com/kubernetes-sigs/kustomize/version.gitCommit={{.Commit}} -X github.com/kubernetes-sigs/kustomize/version.buildDate={{.Date}}
goos:
- darwin
- linux
- windows
goarch:
- amd64
env:
- CGO_ENABLED=0
checksum:
name_template: 'checksums.txt'
archive:
format: binary
snapshot:
name_template: "master"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
- Merge pull request
- Merge branch
release:
github:
owner: kubernetes-sigs
name: kustomize

View File

@@ -184,3 +184,19 @@ vars:
apiVersion: apps/v1
fieldref:
fieldpath: spec.template.spec.restartPolicy
# ImageTags modify the tags for images without creating patches.
# E.g. Given this fragment of a Deployment:
# ```
# containers:
# - name: myapp
# image: mycontainerregistry/myimage:v0
# - name: nginxapp
# image: nginx:1.7.9
#```
# one can change the tag of myimage to v1 and the tag of nginx to 1.8.0 with the following:
imageTags:
- name: mycontainerregistry/myimage
newTag: v1
- name: nginx
newTag: 1.8.0

View File

@@ -23,11 +23,16 @@ go get github.com/kubernetes-sigs/kustomize
* [springboot](springboot/README.md) - Create a Spring Boot
application production configuration from scratch.
* [configGeneration](configGeneration.md) -
* [combineConfigs](combineConfigs.md) -
Mixing configuration data from different owners
(e.g. devops/SRE and developers).
* [configGenerations](configGeneration.md) -
Rolling update when ConfigMapGenerator changes
* [breakfast](breakfast.md) - Customize breakfast for
Alice and Bob.
* [container args](wordpress/README.md) - Injecting k8s runtime data into container arguments (e.g. to point wordpress to a SQL service).
* [image tags](imageTags.md) - Updating image tags without applying a patch.

298
examples/combineConfigs.md Normal file
View File

@@ -0,0 +1,298 @@
[overlay]: ../docs/glossary.md#overlay
[target]: ../docs/glossary.md#target
# Demo: combining config data from devops and developers
Scenario: you have a Java-based server storefront in
production that various internal development teams
(signups, checkout, search, etc.) contribute to.
The server runs in different environments:
_development_, _testing_, _staging_ and _production_,
accepting configuration parameters from java property
files.
Using one big properties file for each environment is
difficult to manage. The files change frequently, and
have to be changed by devops exclusively because
1. the files must at least partially agree on certain
values that devops cares about and that developers
ignore and
1. because the production
properties contain sensitive data like production
database credentials.
## Property sharding
With some study, we notice that the properties are
separable into categories.
### Common properties
E.g. internationalization data, static data like
physical constants, location of external services, etc.
_Things that are the same regardless of environment._
Only one set of values is needed.
Place them in a file called
* `common.properties`
(relative location defined below).
### Plumbing properties
E.g. serving location of static content (HTML, CSS,
javascript), location of product and customer database
tables, ports expected by load balancers, log sinks,
etc.
_The different values for these properties are
precisely what sets the environments apart._
Devops or SRE will want full control over the values
used in production. Testing will have fixed
databases supporting testing. Developers will want
to do whatever they want to try scenarios under
development.
Places these values in
* `development/plumbing.properties`
* `staging/plumbing.properties`
* `production/plumbing.properties`
### Secret properties
E.g. location of actual user tables, database
credentials, decryption keys, etc.
_Things that are a subset of devops controls, that
nobody else has (or should want) access to._
Places these values in
* `development/secret.properties`
* `staging/secret.properties`
* `production/secret.properties`
[kubernetes secret]: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/
and control access to them with (for example) unix file
owner and mode bits, or better yet, put them in
a server dedicated to storing password protected
secrets, and use a field called `secretGenerator`
in your _kustomization_ to create a kubernetes
secret holding them (not covering that here).
<!--
secretGenerator:
- name: app-tls
commands:
tls.crt: "cat tls.cert"
tls.key: "cat tls.key"
type: "kubernetes.io/tls"
EOF
-->
## A mixin approach to management
The way to create _n_ cluster environments that share
some common information is to create _n_ overlays of a
common base.
For the rest of this example, we'll do _n==2_, just
_development_ and _production_, since adding more
environments follows the same pattern.
A cluster environment is created by
running `kustomize build` on a [target] that happens to
be an [overlay].
[helloworld]: helloWorld/README.md
The following example will do that, but will focus on
configMap construction, and not worry about how to
connect the configMaps to deployments (that is covered
in the [helloworld] example).
All files - including the shared property files
discussed above - will be created in a directory tree
that is consistent with the base vs overlay file layout
defined in the [helloworld] demo.
It will all live in this work directory:
<!-- @makeWorkplace @test -->
```
DEMO_HOME=$(mktemp -d)
```
### Create the base
<!-- kubectl create configmap BOB --dry-run -o yaml --from-file db. -->
Make a place to put the base configuration:
<!-- @baseDir @test -->
```
mkdir -p $DEMO_HOME/base
```
Make the data for the base. This direction by
definition should hold resources common to all
environments. Here we're only defining a java
properties file, and a `kustomization` file that
references it.
<!-- @baseKustomization @test -->
```
cat <<EOF >$DEMO_HOME/base/common.properties
color=blue
height=10m
EOF
cat <<EOF >$DEMO_HOME/base/kustomization.yaml
configMapGenerator:
- name: my-configmap
files:
- common.properties
EOF
```
### Create and use the overlay for _development_
Make an abbreviation for the parent of the overlay
directories:
<!-- @overlays @test -->
```
OVERLAYS=$DEMO_HOME/overlays
```
Create the files that define the _development_ overlay:
<!-- @developmentFiles @test -->
```
mkdir -p $OVERLAYS/development
cat <<EOF >$OVERLAYS/development/plumbing.properties
port=30000
EOF
cat <<EOF >$OVERLAYS/development/secret.properties
dbpassword=mothersMaidenName
EOF
cat <<EOF >$OVERLAYS/development/kustomization.yaml
bases:
- ../../base
namePrefix: dev-
configMapGenerator:
- name: my-configmap
behavior: merge
files:
- plumbing.properties
- secret.properties
EOF
```
One can now generate the configMaps for development:
<!-- @runDev @test -->
```
kustomize build $OVERLAYS/development
```
#### Check the ConfigMap name
The name of the generated `ConfigMap` is visible in this
output.
The name should be something like `dev-my-configmap-b5m75ck895`:
* `"dev-"` comes from the `namePrefix` field,
* `"my-configmap"` comes from the `configMapGenerator/name` field,
* `"-b5m75ck895"` comes from a deterministic hash that `kustomize`
computes from the contents of the configMap.
The hash suffix is critical. If the configMap content
changes, so does the configMap name, along with all
references to that name that appear in the YAML output
from `kustomize`.
The name change means deployments will do a rolling
restart to get new data if this YAML is applied to the
cluster using a command like
> ```
> kustomize build $OVERLAYS/development | kubectl apply -f -
> ```
A deployment has no means to automatically know when or
if a configMap in use by the deployment changes.
If one changes a configMap without changing its name
and all references to that name, one must imperatively
restart the cluster to pick up the change.
The best practice is to treat configMaps as immutable.
Instead of editing configMaps, modify your declarative
specification of the cluster's desired state to
point deployments to _new_ configMaps with _new_ names.
`kustomize` makes this easy with its
`configMapGenerator` directive and associated naming
controls. A GC process in the k8s master eventually
deletes unused configMaps.
### Create and use the overlay for _production_
Next, create the files for the _production_ overlay:
<!-- @productionFiles @test -->
```
mkdir -p $OVERLAYS/production
cat <<EOF >$OVERLAYS/production/plumbing.properties
port=8080
EOF
cat <<EOF >$OVERLAYS/production/secret.properties
dbpassword=thisShouldProbablyBeInASecretInstead
EOF
cat <<EOF >$OVERLAYS/production/kustomization.yaml
bases:
- ../../base
namePrefix: prod-
configMapGenerator:
- name: my-configmap
behavior: merge
files:
- plumbing.properties
- secret.properties
EOF
```
One can now generate the configMaps for production:
<!-- @runProd @test -->
```
kustomize build $OVERLAYS/production
```
A CICD process could apply this directly to
the cluser using:
> ```
> kustomize build $OVERLAYS/production | kubectl apply -f -
> ```

View File

@@ -1,298 +1,208 @@
[overlay]: ../docs/glossary.md#overlay
[target]: ../docs/glossary.md#target
[patch]: ../../docs/glossary.md#patch
[resource]: ../../docs/glossary.md#resource
[variant]: ../../docs/glossary.md#variant
# Demo: combining config data from devops and developers
## ConfigMap generation and rolling updates
Scenario: you have a Java-based server storefront in
production that various internal development teams
(signups, checkout, search, etc.) contribute to.
Kustomize provides two ways of adding ConfigMap in one `kustomization`, either by declaring ConfigMap as a [resource] or declaring ConfigMap from a ConfigMapGenerator. The formats inside `kustomization.yaml` are
The server runs in different environments:
_development_, _testing_, _staging_ and _production_,
accepting configuration parameters from java property
files.
> ```
> # declare ConfigMap as a resource
> resources:
> - configmap.yaml
>
> # declare ConfigMap from a ConfigMapGenerator
> configMapGenerator:
> - name: a-configmap
> files:
> - configs/configfile
> - configs/another_configfile
> ```
Using one big properties file for each environment is
difficult to manage. The files change frequently, and
have to be changed by devops exclusively because
The ConfigMaps declared as [resource] are treated the same way as other resources. Kustomize doesn't append any hash to the ConfigMap name. The ConfigMap declared from a ConfigMapGenerator is treated differently. A hash is appended to the name and any change in the ConfigMap will trigger a rolling update.
1. the files must at least partially agree on certain
values that devops cares about and that developers
ignore and
1. because the production
properties contain sensitive data like production
database credentials.
In this demo, the same [hello_world](helloWorld/README.md) is used while the ConfigMap declared as [resources] is replaced by a ConfigMap declared from a ConfigmapGenerator. The change in this ConfigMap will result in a hash change and a rolling update.
## Property sharding
### Establish base and staging
With some study, we notice that the properties are
separable into categories.
### Common properties
E.g. internationalization data, static data like
physical constants, location of external services, etc.
_Things that are the same regardless of environment._
Only one set of values is needed.
Place them in a file called
* `common.properties`
(relative location defined below).
### Plumbing properties
E.g. serving location of static content (HTML, CSS,
javascript), location of product and customer database
tables, ports expected by load balancers, log sinks,
etc.
_The different values for these properties are
precisely what sets the environments apart._
Devops or SRE will want full control over the values
used in production. Testing will have fixed
databases supporting testing. Developers will want
to do whatever they want to try scenarios under
development.
Places these values in
* `development/plumbing.properties`
* `staging/plumbing.properties`
* `production/plumbing.properties`
### Secret properties
E.g. location of actual user tables, database
credentials, decryption keys, etc.
_Things that are a subset of devops controls, that
nobody else has (or should want) access to._
Places these values in
* `development/secret.properties`
* `staging/secret.properties`
* `production/secret.properties`
[kubernetes secret]: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/
and control access to them with (for example) unix file
owner and mode bits, or better yet, put them in
a server dedicated to storing password protected
secrets, and use a field called `secretGenerator`
in your _kustomization_ to create a kubernetes
secret holding them (not covering that here).
<!--
secretGenerator:
- name: app-tls
commands:
tls.crt: "cat tls.cert"
tls.key: "cat tls.key"
type: "kubernetes.io/tls"
EOF
-->
## A mixin approach to management
The way to create _n_ cluster environments that share
some common information is to create _n_ overlays of a
common base.
For the rest of this example, we'll do _n==2_, just
_development_ and _production_, since adding more
environments follows the same pattern.
A cluster environment is created by
running `kustomize build` on a [target] that happens to
be an [overlay].
[helloworld]: helloworld.md
The following example will do that, but will focus on
configMap construction, and not worry about how to
connect the configMaps to deployments (that is covered
in the [helloworld] example).
All files - including the shared property files
discussed above - will be created in a directory tree
that is consistent with the base vs overlay file layout
defined in the [helloworld] demo.
It will all live in this work directory:
<!-- @makeWorkplace @test -->
Establish the base with a configMapGenerator
<!-- @establishBase @test -->
```
DEMO_HOME=$(mktemp -d)
```
### Create the base
BASE=$DEMO_HOME/base
mkdir -p $BASE
<!-- kubectl create configmap BOB --dry-run -o yaml --from-file db. -->
curl -s -o "$BASE/#1.yaml" "https://raw.githubusercontent.com\
/kubernetes-sigs/kustomize\
/master/examples/helloWorld\
/{deployment,service}.yaml"
Make a place to put the base configuration:
<!-- @baseDir @test -->
```
mkdir -p $DEMO_HOME/base
```
Make the data for the base. This direction by
definition should hold resources common to all
environments. Here we're only defining a java
properties file, and a `kustomization` file that
references it.
<!-- @baseKustomization @test -->
```
cat <<EOF >$DEMO_HOME/base/common.properties
color=blue
height=10m
EOF
cat <<EOF >$DEMO_HOME/base/kustomization.yaml
configMapGenerator:
- name: my-configmap
files:
- common.properties
cat <<'EOF' >$BASE/kustomization.yaml
commonLabels:
app: hello
resources:
- deployment.yaml
- service.yaml
configMapGenerator:
- name: the-map
literals:
- altGreeting=Good Morning!
- enableRisky="false"
EOF
```
### Create and use the overlay for _development_
Make an abbreviation for the parent of the overlay
directories:
<!-- @overlays @test -->
Establish the staging with a patch applied to the ConfigMap
<!-- @establishStaging @test -->
```
OVERLAYS=$DEMO_HOME/overlays
```
mkdir -p $OVERLAYS/staging
Create the files that define the _development_ overlay:
<!-- @developmentFiles @test -->
```
mkdir -p $OVERLAYS/development
cat <<EOF >$OVERLAYS/development/plumbing.properties
port=30000
EOF
cat <<EOF >$OVERLAYS/development/secret.properties
dbpassword=mothersMaidenName
EOF
cat <<EOF >$OVERLAYS/development/kustomization.yaml
cat <<'EOF' >$OVERLAYS/staging/kustomization.yaml
namePrefix: staging-
commonLabels:
variant: staging
org: acmeCorporation
commonAnnotations:
note: Hello, I am staging!
bases:
- ../../base
namePrefix: dev-
configMapGenerator:
- name: my-configmap
behavior: merge
files:
- plumbing.properties
- secret.properties
patches:
- map.yaml
EOF
cat <<EOF >$OVERLAYS/staging/map.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: the-map
data:
altGreeting: "Have a pineapple!"
enableRisky: "true"
EOF
```
One can now generate the configMaps for development:
### Review
<!-- @runDev @test -->
The _hello-world_ deployment running in this cluster is
configured with data from a configMap.
The deployment refers to this map by name:
<!-- @showDeployment @test -->
```
kustomize build $OVERLAYS/development
grep -C 2 configMapKeyRef $BASE/deployment.yaml
```
#### Check the ConfigMap name
Changing the data held by a live configMap in a cluster
is considered bad practice. Deployments have no means
to know that the configMaps they refer to have
changed, so such updates have no effect.
The name of the generated `ConfigMap` is visible in this
output.
The recommended way to change a deployment's
configuration is to
The name should be something like `dev-my-configmap-b5m75ck895`:
1. create a new configMap with a new name,
1. patch the _deployment_, modifying the name value of
the appropriate `configMapKeyRef` field.
* `"dev-"` comes from the `namePrefix` field,
* `"my-configmap"` comes from the `configMapGenerator/name` field,
* `"-b5m75ck895"` comes from a deterministic hash that `kustomize`
computes from the contents of the configMap.
This latter change initiates rolling update to the pods
in the deployment. The older configMap, when no longer
referenced by any other resource, is eventually garbage
collected.
The hash suffix is critical. If the configMap content
changes, so does the configMap name, along with all
references to that name that appear in the YAML output
from `kustomize`.
### How this works with kustomize
The name change means deployments will do a rolling
restart to get new data if this YAML is applied to the
cluster using a command like
The _staging_ [variant] here has a configMap [patch]:
> ```
> kustomize build $OVERLAYS/development | kubectl apply -f -
> ```
A deployment has no means to automatically know when or
if a configMap in use by the deployment changes.
If one changes a configMap without changing its name
and all references to that name, one must imperatively
restart the cluster to pick up the change.
The best practice is to treat configMaps as immutable.
Instead of editing configMaps, modify your declarative
specification of the cluster's desired state to
point deployments to _new_ configMaps with _new_ names.
`kustomize` makes this easy with its
`configMapGenerator` directive and associated naming
controls. A GC process in the k8s master eventually
deletes unused configMaps.
### Create and use the overlay for _production_
Next, create the files for the _production_ overlay:
<!-- @productionFiles @test -->
<!-- @showMapPatch @test -->
```
mkdir -p $OVERLAYS/production
cat <<EOF >$OVERLAYS/production/plumbing.properties
port=8080
EOF
cat <<EOF >$OVERLAYS/production/secret.properties
dbpassword=thisShouldProbablyBeInASecretInstead
EOF
cat <<EOF >$OVERLAYS/production/kustomization.yaml
bases:
- ../../base
namePrefix: prod-
configMapGenerator:
- name: my-configmap
behavior: merge
files:
- plumbing.properties
- secret.properties
EOF
cat $OVERLAYS/staging/map.yaml
```
One can now generate the configMaps for production:
This patch is by definition a named but not necessarily
complete resource spec intended to modify a complete
resource spec.
<!-- @runProd @test -->
The ConfigMap it modifies is declared from a configMapGenerator.
<!-- @showMapBase @test -->
```
kustomize build $OVERLAYS/production
grep -C 4 configMapGenerator $BASE/kustomization.yaml
```
A CICD process could apply this directly to
the cluser using:
For a patch to work, the names in the `metadata/name`
fields must match.
> ```
> kustomize build $OVERLAYS/production | kubectl apply -f -
> ```
However, the name values specified in the file are
_not_ what gets used in the cluster. By design,
kustomize modifies names of ConfigMaps declared from ConfigMapGenerator. To see the names
ultimately used in the cluster, just run kustomize:
<!-- @grepStagingName @test -->
```
kustomize build $OVERLAYS/staging |\
grep -B 8 -A 1 staging-the-map
```
The configMap name is prefixed by _staging-_, per the
`namePrefix` field in
`$OVERLAYS/staging/kustomization.yaml`.
The suffix to the configMap name is generated from a
hash of the maps content - in this case the name suffix
is _hhhhkfmgmk_:
<!-- @grepStagingHash @test -->
```
kustomize build $OVERLAYS/staging | grep hhhhkfmgmk
```
Now modify the map patch, to change the greeting
the server will use:
<!-- @changeMap @test -->
```
sed -i 's/pineapple/kiwi/' $OVERLAYS/staging/map.yaml
```
See the new greeting:
```
kustomize build $OVERLAYS/staging |\
grep -B 2 -A 3 kiwi
```
Run kustomize again to see the new configMap names:
<!-- @grepStagingName @test -->
```
kustomize build $OVERLAYS/staging |\
grep -B 8 -A 1 staging-the-map
```
Confirm that the change in configMap content resulted
in three new names ending in _khk45ktkd9_ - one in the
configMap name itself, and two in the deployment that
uses the map:
<!-- @countHashes @test -->
```
test 3 == \
$(kustomize build $OVERLAYS/staging | grep khk45ktkd9 | wc -l); \
echo $?
```
Applying these resources to the cluster will result in
a rolling update of the deployments pods, retargetting
them from the _hhhhkfmgmk_ maps to the _khk45ktkd9_
maps. The system will later garbage collect the
unused maps.
## Rollback
To rollback, one would undo whatever edits were made to
the configuation in source control, then rerun kustomize
on the reverted configuration and apply it to the
cluster.

View File

@@ -315,130 +315,3 @@ To deploy, pipe the above commands to kubectl apply:
> kustomize build $OVERLAYS/production |\
> kubectl apply -f -
> ```
## Rolling updates
### Review
The _hello-world_ deployment running in this cluster is
configured with data from a configMap.
The deployment refers to this map by name:
<!-- @showDeployment @test -->
```
grep -C 2 configMapKeyRef $DEMO_HOME/base/deployment.yaml
```
Changing the data held by a live configMap in a cluster
is considered bad practice. Deployments have no means
to know that the configMaps they refer to have
changed, so such updates have no effect.
The recommended way to change a deployment's
configuration is to
1. create a new configMap with a new name,
1. patch the _deployment_, modifying the name value of
the appropriate `configMapKeyRef` field.
This latter change initiates rolling update to the pods
in the deployment. The older configMap, when no longer
referenced by any other resource, is eventually garbage
collected.
### How this works with kustomize
The _staging_ [variant] here has a configMap [patch]:
<!-- @showMapPatch @test -->
```
cat $OVERLAYS/staging/map.yaml
```
This patch is by definition a named but not necessarily
complete resource spec intended to modify a complete
resource spec.
The resource it modifies is here:
<!-- @showMapBase @test -->
```
cat $DEMO_HOME/base/configMap.yaml
```
For a patch to work, the names in the `metadata/name`
fields must match.
However, the name values specified in the file are
_not_ what gets used in the cluster. By design,
kustomize modifies these names. To see the names
ultimately used in the cluster, just run kustomize:
<!-- @grepStagingName @test -->
```
kustomize build $OVERLAYS/staging |\
grep -B 8 -A 1 staging-the-map
```
The configMap name is prefixed by _staging-_, per the
`namePrefix` field in
`$OVERLAYS/staging/kustomization.yaml`.
The suffix to the configMap name is generated from a
hash of the maps content - in this case the name suffix
is _hhhhkfmgmk_:
<!-- @grepStagingHash @test -->
```
kustomize build $OVERLAYS/staging | grep hhhhkfmgmk
```
Now modify the map patch, to change the greeting
the server will use:
<!-- @changeMap @test -->
```
sed -i 's/pineapple/kiwi/' $OVERLAYS/staging/map.yaml
```
See the new greeting:
```
kustomize build $OVERLAYS/staging |\
grep -B 2 -A 3 kiwi
```
Run kustomize again to see the new configMap names:
<!-- @grepStagingName @test -->
```
kustomize build $OVERLAYS/staging |\
grep -B 8 -A 1 staging-the-map
```
Confirm that the change in configMap content resulted
in three new names ending in _khk45ktkd9_ - one in the
configMap name itself, and two in the deployment that
uses the map:
<!-- @countHashes @test -->
```
test 3 == \
$(kustomize build $OVERLAYS/staging | grep khk45ktkd9 | wc -l); \
echo $?
```
Applying these resources to the cluster will result in
a rolling update of the deployments pods, retargetting
them from the _hhhhkfmgmk_ maps to the _khk45ktkd9_
maps. The system will later garbage collect the
unused maps.
## Rollback
To rollback, one would undo whatever edits were made to
the configuation in source control, then rerun kustomize
on the reverted configuration and apply it to the
cluster.

View File

@@ -6,9 +6,4 @@ commonLabels:
resources:
- deployment.yaml
- service.yaml
configMapGenerator:
- name: the-map
literals:
- altGreeting="Good Morning!"
- enableRisky="false"
- configMap.yaml

75
examples/imageTags.md Normal file
View File

@@ -0,0 +1,75 @@
# Demo: change image tags
Define a place to work:
<!-- @makeWorkplace @test -->
```
DEMO_HOME=$(mktemp -d)
```
Make a `kustomization` containing a pod resource
<!-- @createKustomization @test -->
```
cat <<EOF >$DEMO_HOME/kustomization.yaml
resources:
- pod.yaml
EOF
```
Declare the pod resource
<!-- @createDeployment @test -->
```
cat <<EOF >$DEMO_HOME/pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox:1.29.0
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: init-mydb
image: busybox:1.29.0
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
EOF
```
The `myapp-pod` resource declares an initContainer and a container, both use the image `busybox:1.29.0`.
The tag `1.29.0` can be changed by adding `imageTags` in `kustomization.yaml`.
Add `imageTags`:
<!-- @addImageTags @test -->
```
cd $DEMO_HOME
kustomize edit set imagetag busybox:1.29.1
```
The `kustomization.yaml` will be added following `imageTags`.
> ```
> imageTags:
> - name: busybox
> newTag: 1.29.1
> ```
Now build this `kustomization`
<!-- @kustomizeBuild @test -->
```
kustomize build $DEMO_HOME
```
Confirm that this replaces _both_ busybox tags:
<!-- @confirmTags @test -->
```
test 2 == \
$(kustomize build $DEMO_HOME | grep busybox:1.29.1 | wc -l); \
echo $?
```

View File

@@ -26,8 +26,10 @@ import (
"github.com/ghodss/yaml"
"github.com/golang/glog"
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/crds"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
interror "github.com/kubernetes-sigs/kustomize/pkg/internal/error"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
@@ -43,12 +45,13 @@ import (
// https://github.com/kubernetes-sigs/kustomize/blob/master/docs/glossary.md#target
type Application struct {
kustomization *types.Kustomization
loader loader.Loader
ldr loader.Loader
fSys fs.FileSystem
}
// NewApplication returns a new instance of Application primed with a Loader.
func NewApplication(loader loader.Loader) (*Application, error) {
content, err := loader.Load(constants.KustomizationFileName)
func NewApplication(ldr loader.Loader, fSys fs.FileSystem) (*Application, error) {
content, err := ldr.Load(constants.KustomizationFileName)
if err != nil {
return nil, err
}
@@ -58,7 +61,8 @@ func NewApplication(loader loader.Loader) (*Application, error) {
if err != nil {
return nil, err
}
return &Application{kustomization: &m, loader: loader}, nil
return &Application{kustomization: &m, ldr: ldr, fSys: fSys}, nil
}
func unmarshal(y []byte, o interface{}) error {
@@ -103,7 +107,7 @@ func (a *Application) resolveRefsToGeneratedResources(m resmap.ResMap) (resmap.R
return nil, err
}
r := []transformers.Transformer{}
var r []transformers.Transformer
t, err := transformers.NewDefaultingNameReferenceTransformer()
if err != nil {
return nil, err
@@ -134,16 +138,19 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
if err != nil {
errs.Append(errors.Wrap(err, "loadResMapFromBasesAndResources"))
}
err = crds.RegisterCRDs(a.loader, a.kustomization.CRDs)
err = crds.RegisterCRDs(a.ldr, a.kustomization.CRDs)
if err != nil {
errs.Append(errors.Wrap(err, "RegisterCRDs"))
}
cms, err := resmap.NewResMapFromConfigMapArgs(a.loader, a.kustomization.ConfigMapGenerator)
cms, err := resmap.NewResMapFromConfigMapArgs(
configmapandsecret.NewConfigMapFactory(a.fSys, a.ldr),
a.kustomization.ConfigMapGenerator)
if err != nil {
errs.Append(errors.Wrap(err, "NewResMapFromConfigMapArgs"))
}
secrets, err := resmap.NewResMapFromSecretArgs(a.loader.Root(), a.kustomization.SecretGenerator)
secrets, err := resmap.NewResMapFromSecretArgs(
configmapandsecret.NewSecretFactory(a.fSys, a.ldr.Root()),
a.kustomization.SecretGenerator)
if err != nil {
errs.Append(errors.Wrap(err, "NewResMapFromSecretArgs"))
}
@@ -157,7 +164,7 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
return nil, err
}
patches, err := resmap.NewResourceSliceFromPatches(a.loader, a.kustomization.Patches)
patches, err := resmap.NewResourceSliceFromPatches(a.ldr, a.kustomization.Patches)
if err != nil {
errs.Append(errors.Wrap(err, "NewResourceSliceFromPatches"))
}
@@ -165,11 +172,20 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
if len(errs.Get()) > 0 {
return nil, errs
}
var r []transformers.Transformer
t, err := a.newTransformer(patches)
if err != nil {
return nil, err
}
err = t.Transform(result)
r = append(r, t)
t, err = transformers.NewImageTagTransformer(a.kustomization.ImageTags)
if err != nil {
return nil, err
}
r = append(r, t)
err = transformers.NewMultiTransformer(r).Transform(result)
if err != nil {
return nil, err
}
@@ -179,7 +195,7 @@ func (a *Application) loadCustomizedResMap() (resmap.ResMap, error) {
// Gets Bases and Resources as advertised.
func (a *Application) loadResMapFromBasesAndResources() (resmap.ResMap, error) {
bases, errs := a.loadCustomizedBases()
resources, err := resmap.NewResMapFromFiles(a.loader, a.kustomization.Resources)
resources, err := resmap.NewResMapFromFiles(a.ldr, a.kustomization.Resources)
if err != nil {
errs.Append(errors.Wrap(err, "rawResources failed to read Resources"))
}
@@ -192,15 +208,15 @@ func (a *Application) loadResMapFromBasesAndResources() (resmap.ResMap, error) {
// Loop through the Bases of this kustomization recursively loading resources.
// Combine into one ResMap, demanding unique Ids for each resource.
func (a *Application) loadCustomizedBases() (resmap.ResMap, *interror.KustomizationErrors) {
list := []resmap.ResMap{}
var list []resmap.ResMap
errs := &interror.KustomizationErrors{}
for _, path := range a.kustomization.Bases {
loader, err := a.loader.New(path)
ldr, err := a.ldr.New(path)
if err != nil {
errs.Append(errors.Wrap(err, "couldn't make loader for "+path))
errs.Append(errors.Wrap(err, "couldn't make ldr for "+path))
continue
}
app, err := NewApplication(loader)
app, err := NewApplication(ldr, a.fSys)
if err != nil {
errs.Append(errors.Wrap(err, "couldn't make app for "+path))
continue
@@ -223,12 +239,12 @@ func (a *Application) loadBasesAsFlatList() ([]*Application, error) {
var result []*Application
errs := &interror.KustomizationErrors{}
for _, path := range a.kustomization.Bases {
loader, err := a.loader.New(path)
ldr, err := a.ldr.New(path)
if err != nil {
errs.Append(err)
continue
}
a, err := NewApplication(loader)
a, err := NewApplication(ldr, a.fSys)
if err != nil {
errs.Append(err)
continue
@@ -243,7 +259,7 @@ func (a *Application) loadBasesAsFlatList() ([]*Application, error) {
// newTransformer makes a Transformer that does everything except resolve generated names.
func (a *Application) newTransformer(patches []*resource.Resource) (transformers.Transformer, error) {
r := []transformers.Transformer{}
var r []transformers.Transformer
t, err := transformers.NewPatchTransformer(patches)
if err != nil {
return nil, err
@@ -291,7 +307,7 @@ func (a *Application) resolveRefVars(m resmap.ResMap) (map[string]string, error)
// getAllVars returns all the "environment" style Var instances defined in the app.
func (a *Application) getAllVars() ([]types.Var, error) {
result := []types.Var{}
var result []types.Var
errs := &interror.KustomizationErrors{}
bases, err := a.loadBasesAsFlatList()

View File

@@ -18,11 +18,11 @@ package app
import (
"encoding/base64"
"os"
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/internal/loadertest"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
@@ -67,20 +67,20 @@ metadata:
)
func makeLoader1(t *testing.T) loader.Loader {
loader := loadertest.NewFakeLoader("/testpath")
err := loader.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContent1))
ldr := loadertest.NewFakeLoader("/testpath")
err := ldr.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContent1))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
err = loader.AddFile("/testpath/deployment.yaml", []byte(deploymentContent))
err = ldr.AddFile("/testpath/deployment.yaml", []byte(deploymentContent))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
err = loader.AddFile("/testpath/namespace.yaml", []byte(namespaceContent))
err = ldr.AddFile("/testpath/namespace.yaml", []byte(namespaceContent))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
return loader
return ldr
}
var deploy = schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"}
@@ -142,7 +142,7 @@ func TestResources1(t *testing.T) {
"DB_USERNAME": "admin",
"DB_PASSWORD": "somepw",
},
}),
}).SetBehavior(resource.BehaviorCreate),
resource.NewResId(secret, "secret"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
@@ -163,7 +163,7 @@ func TestResources1(t *testing.T) {
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
},
}),
}).SetBehavior(resource.BehaviorCreate),
resource.NewResId(ns, "ns1"): resource.NewResourceFromMap(
map[string]interface{}{
"apiVersion": "v1",
@@ -180,7 +180,9 @@ func TestResources1(t *testing.T) {
}),
}
l := makeLoader1(t)
app, err := NewApplication(l)
fakeFs := fs.MakeFakeFS()
fakeFs.Mkdir("/")
app, err := NewApplication(l, fakeFs)
if err != nil {
t.Fatalf("Unexpected construction error %v", err)
}
@@ -215,7 +217,7 @@ func TestRawResources1(t *testing.T) {
}),
}
l := makeLoader1(t)
app, err := NewApplication(l)
app, err := NewApplication(l, fs.MakeFakeFS())
if err != nil {
t.Fatalf("Unexpected construction error %v", err)
}
@@ -255,28 +257,28 @@ spec:
)
func makeLoader2(t *testing.T) loader.Loader {
loader := loadertest.NewFakeLoader("/testpath")
err := loader.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContentOverlay))
ldr := loadertest.NewFakeLoader("/testpath")
err := ldr.AddFile("/testpath/"+constants.KustomizationFileName, []byte(kustomizationContentOverlay))
if err != nil {
t.Fatal(err)
}
err = loader.AddFile("/testpath/service.yaml", []byte(serviceContent))
err = ldr.AddFile("/testpath/service.yaml", []byte(serviceContent))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
err = loader.AddDirectory("/testpath/base", os.ModeDir)
err = ldr.AddDirectory("/testpath/base")
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
err = loader.AddFile("/testpath/base/"+constants.KustomizationFileName, []byte(kustomizationContentBase))
err = ldr.AddFile("/testpath/base/"+constants.KustomizationFileName, []byte(kustomizationContentBase))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
err = loader.AddFile("/testpath/base/deployment.yaml", []byte(deploymentContent))
err = ldr.AddFile("/testpath/base/deployment.yaml", []byte(deploymentContent))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
return loader
return ldr
}
// TODO: This test covers incorrect behavior; it should not pass.
@@ -325,7 +327,7 @@ func TestRawResources2(t *testing.T) {
}),
}
l := makeLoader2(t)
app, err := NewApplication(l)
app, err := NewApplication(l, fs.MakeFakeFS())
if err != nil {
t.Fatalf("Unexpected construction error %v", err)
}

View File

@@ -84,9 +84,8 @@ func (o *addBaseOptions) RunAddBase(fsys fs.FileSystem) error {
// split directory paths
paths := strings.Split(o.baseDirectoryPaths, ",")
for _, path := range paths {
_, err := fsys.Stat(path)
if err != nil {
return err
if !fsys.Exists(path) {
return errors.New(path + " does not exist")
}
if stringInSlice(path, m.Bases) {
return fmt.Errorf("base %s already in kustomization file", path)

View File

@@ -33,7 +33,7 @@ func TestAddBaseHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
bases := strings.Split(baseDirectoryPaths, ",")
for _, base := range bases {
fakeFS.Mkdir(base, 0777)
fakeFS.Mkdir(base)
}
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
@@ -60,7 +60,7 @@ func TestAddBaseAlreadyThere(t *testing.T) {
// Create fake directories
bases := strings.Split(baseDirectoryPaths, ",")
for _, base := range bases {
fakeFS.Mkdir(base, 0777)
fakeFS.Mkdir(base)
}
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
@@ -77,9 +77,9 @@ func TestAddBaseAlreadyThere(t *testing.T) {
}
var expectedErrors []string
for _, base := range bases {
error := "base " + base + " already in kustomization file"
expectedErrors = append(expectedErrors, error)
if !stringInSlice(error, expectedErrors) {
msg := "base " + base + " already in kustomization file"
expectedErrors = append(expectedErrors, msg)
if !stringInSlice(msg, expectedErrors) {
t.Errorf("unexpected error %v", err)
}
}

View File

@@ -70,9 +70,8 @@ func (o *addPatchOptions) Complete(cmd *cobra.Command, args []string) error {
// RunAddPatch runs addPatch command (do real work).
func (o *addPatchOptions) RunAddPatch(fsys fs.FileSystem) error {
_, err := fsys.Stat(o.patchFilePath)
if err != nil {
return err
if !fsys.Exists(o.patchFilePath) {
return errors.New(o.patchFilePath + " doesn't exist")
}
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)

View File

@@ -70,11 +70,9 @@ func (o *addResourceOptions) Complete(cmd *cobra.Command, args []string) error {
// RunAddResource runs addResource command (do real work).
func (o *addResourceOptions) RunAddResource(fsys fs.FileSystem) error {
_, err := fsys.Stat(o.resourceFilePath)
if err != nil {
return err
if !fsys.Exists(o.resourceFilePath) {
return errors.New(o.resourceFilePath + " does not exist")
}
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err

View File

@@ -69,8 +69,8 @@ func (o *buildOptions) Validate(args []string) error {
}
// RunBuild runs build command.
func (o *buildOptions) RunBuild(out io.Writer, fs fs.FileSystem) error {
l := loader.Init([]loader.SchemeLoader{loader.NewFileLoader(fs)})
func (o *buildOptions) RunBuild(out io.Writer, fSys fs.FileSystem) error {
l := loader.NewLoader(loader.NewFileLoader(fSys))
absPath, err := filepath.Abs(o.kustomizationPath)
if err != nil {
@@ -82,7 +82,7 @@ func (o *buildOptions) RunBuild(out io.Writer, fs fs.FileSystem) error {
return err
}
application, err := app.NewApplication(rootLoader)
application, err := app.NewApplication(rootLoader, fSys)
if err != nil {
return err
}

View File

@@ -79,7 +79,7 @@ func TestBuildValidate(t *testing.T) {
func TestBuild(t *testing.T) {
const updateEnvVar = "UPDATE_KUSTOMIZE_EXPECTED_DATA"
updateKustomizeExpected := os.Getenv(updateEnvVar) == "true"
fs := fs.MakeRealFS()
fSys := fs.MakeRealFS()
testcases := sets.NewString()
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
@@ -104,12 +104,12 @@ func TestBuild(t *testing.T) {
}
for _, testcaseName := range testcases.List() {
t.Run(testcaseName, func(t *testing.T) { runBuildTestCase(t, testcaseName, updateKustomizeExpected, fs) })
t.Run(testcaseName, func(t *testing.T) { runBuildTestCase(t, testcaseName, updateKustomizeExpected, fSys) })
}
}
func runBuildTestCase(t *testing.T, testcaseName string, updateKustomizeExpected bool, fs fs.FileSystem) {
func runBuildTestCase(t *testing.T, testcaseName string, updateKustomizeExpected bool, fSys fs.FileSystem) {
name := testcaseName
testcase := buildTestCase{}
testcaseDir := filepath.Join("testdata", "testcase-"+name)
@@ -125,7 +125,7 @@ func runBuildTestCase(t *testing.T, testcaseName string, updateKustomizeExpected
kustomizationPath: testcase.Filename,
}
buf := bytes.NewBuffer([]byte{})
err = ops.RunBuild(buf, fs)
err = ops.RunBuild(buf, fSys)
switch {
case err != nil && len(testcase.ExpectedError) == 0:
t.Errorf("unexpected error: %v", err)

View File

@@ -20,8 +20,8 @@ import (
"fmt"
)
// dataConfig encapsulates the options for add configmap/Secret commands.
type dataConfig struct {
// cMapFlagsAndArgs encapsulates the options for add configmap commands.
type cMapFlagsAndArgs struct {
// Name of configMap/Secret (required)
Name string
// FileSources to derive the configMap/Secret from (optional)
@@ -34,7 +34,7 @@ type dataConfig struct {
}
// Validate validates required fields are set to support structured generation.
func (a *dataConfig) Validate(args []string) error {
func (a *cMapFlagsAndArgs) Validate(args []string) error {
if len(args) != 1 {
return fmt.Errorf("name must be specified once")
}

View File

@@ -21,7 +21,7 @@ import (
)
func TestDataConfigValidation_NoName(t *testing.T) {
config := dataConfig{}
config := cMapFlagsAndArgs{}
if config.Validate([]string{}) == nil {
t.Fatal("Validation should fail if no name is specified")
@@ -29,7 +29,7 @@ func TestDataConfigValidation_NoName(t *testing.T) {
}
func TestDataConfigValidation_MoreThanOneName(t *testing.T) {
config := dataConfig{}
config := cMapFlagsAndArgs{}
if config.Validate([]string{"name", "othername"}) == nil {
t.Fatal("Validation should fail if more than one name is specified")
@@ -39,12 +39,12 @@ func TestDataConfigValidation_MoreThanOneName(t *testing.T) {
func TestDataConfigValidation_Flags(t *testing.T) {
tests := []struct {
name string
config dataConfig
config cMapFlagsAndArgs
shouldFail bool
}{
{
name: "env-file-source and literal are both set",
config: dataConfig{
config: cMapFlagsAndArgs{
LiteralSources: []string{"one", "two"},
EnvFileSource: "three",
},
@@ -52,7 +52,7 @@ func TestDataConfigValidation_Flags(t *testing.T) {
},
{
name: "env-file-source and from-file are both set",
config: dataConfig{
config: cMapFlagsAndArgs{
FileSources: []string{"one", "two"},
EnvFileSource: "three",
},
@@ -60,12 +60,12 @@ func TestDataConfigValidation_Flags(t *testing.T) {
},
{
name: "we don't have any option set",
config: dataConfig{},
config: cMapFlagsAndArgs{},
shouldFail: true,
},
{
name: "we have from-file and literal ",
config: dataConfig{
config: cMapFlagsAndArgs{
LiteralSources: []string{"one", "two"},
FileSources: []string{"three", "four"},
},

View File

@@ -123,6 +123,7 @@ func newCmdSet(fsys fs.FileSystem) *cobra.Command {
c.AddCommand(
newCmdSetNamePrefix(fsys),
newCmdSetImageTag(fsys),
)
return c
}

View File

@@ -24,11 +24,12 @@ import (
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/types"
)
func newCmdAddConfigMap(fsys fs.FileSystem) *cobra.Command {
var config dataConfig
func newCmdAddConfigMap(fSys fs.FileSystem) *cobra.Command {
var flagsAndArgs cMapFlagsAndArgs
cmd := &cobra.Command{
Use: "configmap NAME [--from-file=[key=]source] [--from-literal=key1=value1]",
Short: "Adds a configmap to the kustomization file.",
@@ -44,47 +45,50 @@ func newCmdAddConfigMap(fsys fs.FileSystem) *cobra.Command {
kustomize edit add configmap my-configmap --from-env-file=env/path.env
`,
RunE: func(_ *cobra.Command, args []string) error {
err := config.Validate(args)
err := flagsAndArgs.Validate(args)
if err != nil {
return err
}
// Load in the kustomization file.
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
// Load the kustomization file.
mf, err := newKustomizationFile(constants.KustomizationFileName, fSys)
if err != nil {
return err
}
m, err := mf.read()
kustomization, err := mf.read()
if err != nil {
return err
}
// Add the config map to the kustomization file.
err = addConfigMap(m, config)
// Add the flagsAndArgs map to the kustomization file.
err = addConfigMap(
kustomization, flagsAndArgs,
configmapandsecret.NewConfigMapFactory(
fSys, loader.NewLoader(loader.NewFileLoader(fSys))))
if err != nil {
return err
}
// Write out the kustomization file with added configmap.
return mf.write(m)
return mf.write(kustomization)
},
}
cmd.Flags().StringSliceVar(
&config.FileSources,
&flagsAndArgs.FileSources,
"from-file",
[]string{},
"Key file can be specified using its file path, in which case file basename will be used as configmap "+
"key, or optionally with a key and file path, in which case the given key will be used. Specifying a "+
"directory will iterate each named file in the directory whose basename is a valid configmap key.")
cmd.Flags().StringArrayVar(
&config.LiteralSources,
&flagsAndArgs.LiteralSources,
"from-literal",
[]string{},
"Specify a key and literal value to insert in configmap (i.e. mykey=somevalue)")
cmd.Flags().StringVar(
&config.EnvFileSource,
&flagsAndArgs.EnvFileSource,
"from-env-file",
"",
"Specify the path to a file to read lines of key=val pairs to create a configmap (i.e. a Docker .env file).")
@@ -92,27 +96,27 @@ func newCmdAddConfigMap(fsys fs.FileSystem) *cobra.Command {
return cmd
}
// addConfigMap updates a configmap within a kustomization file, using the data in config.
// Note: error may leave kustomization file in an undefined state. Suggest passing a copy
// of kustomization file.
func addConfigMap(m *types.Kustomization, config dataConfig) error {
cm := getOrCreateConfigMap(m, config.Name)
err := mergeData(&cm.DataSources, config)
// addConfigMap adds a configmap to a kustomization file.
// Note: error may leave kustomization file in an undefined state.
// Suggest passing a copy of kustomization file.
func addConfigMap(
k *types.Kustomization,
flagsAndArgs cMapFlagsAndArgs,
factory *configmapandsecret.ConfigMapFactory) error {
cmArgs := makeConfigMapArgs(k, flagsAndArgs.Name)
err := mergeFlagsIntoCmArgs(&cmArgs.DataSources, flagsAndArgs)
if err != nil {
return err
}
// Validate by trying to create corev1.configmap.
_, _, err = configmapandsecret.MakeConfigmapAndGenerateName(*cm)
_, _, err = factory.MakeUnstructAndGenerateName(cmArgs)
if err != nil {
return err
}
return nil
}
func getOrCreateConfigMap(m *types.Kustomization, name string) *types.ConfigMapArgs {
func makeConfigMapArgs(m *types.Kustomization, name string) *types.ConfigMapArgs {
for i, v := range m.ConfigMapGenerator {
if name == v.Name {
return &m.ConfigMapGenerator[i]
@@ -124,13 +128,12 @@ func getOrCreateConfigMap(m *types.Kustomization, name string) *types.ConfigMapA
return &m.ConfigMapGenerator[len(m.ConfigMapGenerator)-1]
}
func mergeData(src *types.DataSources, config dataConfig) error {
src.LiteralSources = append(src.LiteralSources, config.LiteralSources...)
src.FileSources = append(src.FileSources, config.FileSources...)
if src.EnvSource != "" && src.EnvSource != config.EnvFileSource {
return fmt.Errorf("updating existing env source '%s' not allowed.", src.EnvSource)
func mergeFlagsIntoCmArgs(src *types.DataSources, flags cMapFlagsAndArgs) error {
src.LiteralSources = append(src.LiteralSources, flags.LiteralSources...)
src.FileSources = append(src.FileSources, flags.FileSources...)
if src.EnvSource != "" && src.EnvSource != flags.EnvFileSource {
return fmt.Errorf("updating existing env source '%s' not allowed", src.EnvSource)
}
src.EnvSource = config.EnvFileSource
src.EnvSource = flags.EnvFileSource
return nil
}

View File

@@ -29,7 +29,7 @@ func TestNewAddConfigMapIsNotNil(t *testing.T) {
}
}
func TestGetOrCreateConfigMap(t *testing.T) {
func TestMakeConfigMapArgs(t *testing.T) {
cmName := "test-config-name"
kustomization := &types.Kustomization{
@@ -39,24 +39,24 @@ func TestGetOrCreateConfigMap(t *testing.T) {
if len(kustomization.ConfigMapGenerator) != 0 {
t.Fatal("Initial kustomization should not have any configmaps")
}
cm := getOrCreateConfigMap(kustomization, cmName)
args := makeConfigMapArgs(kustomization, cmName)
if cm == nil {
t.Fatalf("ConfigMap should always be non-nil")
if args == nil {
t.Fatalf("args should always be non-nil")
}
if len(kustomization.ConfigMapGenerator) != 1 {
t.Fatalf("Kustomization should have newly created configmap")
}
if &kustomization.ConfigMapGenerator[len(kustomization.ConfigMapGenerator)-1] != cm {
t.Fatalf("Pointer address for newly inserted configmap should be same")
if &kustomization.ConfigMapGenerator[len(kustomization.ConfigMapGenerator)-1] != args {
t.Fatalf("Pointer address for newly inserted configmap generator should be same")
}
existingCM := getOrCreateConfigMap(kustomization, cmName)
args2 := makeConfigMapArgs(kustomization, cmName)
if existingCM != cm {
t.Fatalf("should have returned an existing cm with name: %v", cmName)
if args2 != args {
t.Fatalf("should have returned an existing args with name: %v", cmName)
}
if len(kustomization.ConfigMapGenerator) != 1 {
@@ -64,10 +64,10 @@ func TestGetOrCreateConfigMap(t *testing.T) {
}
}
func TestMergeData_LiteralSources(t *testing.T) {
func TestMergeFlagsIntoCmArgs_LiteralSources(t *testing.T) {
ds := &types.DataSources{}
err := mergeData(ds, dataConfig{LiteralSources: []string{"k1=v1"}})
err := mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{LiteralSources: []string{"k1=v1"}})
if err != nil {
t.Fatalf("Merge initial literal source should not return error")
}
@@ -76,7 +76,7 @@ func TestMergeData_LiteralSources(t *testing.T) {
t.Fatalf("Initial literal source should have been added")
}
err = mergeData(ds, dataConfig{LiteralSources: []string{"k2=v2"}})
err = mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{LiteralSources: []string{"k2=v2"}})
if err != nil {
t.Fatalf("Merge second literal source should not return error")
}
@@ -86,10 +86,10 @@ func TestMergeData_LiteralSources(t *testing.T) {
}
}
func TestMergeData_FileSources(t *testing.T) {
func TestMergeFlagsIntoCmArgs_FileSources(t *testing.T) {
ds := &types.DataSources{}
err := mergeData(ds, dataConfig{FileSources: []string{"file1"}})
err := mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{FileSources: []string{"file1"}})
if err != nil {
t.Fatalf("Merge initial file source should not return error")
}
@@ -98,7 +98,7 @@ func TestMergeData_FileSources(t *testing.T) {
t.Fatalf("Initial file source should have been added")
}
err = mergeData(ds, dataConfig{FileSources: []string{"file2"}})
err = mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{FileSources: []string{"file2"}})
if err != nil {
t.Fatalf("Merge second file source should not return error")
}
@@ -108,12 +108,12 @@ func TestMergeData_FileSources(t *testing.T) {
}
}
func TestMergeData_EnvSource(t *testing.T) {
func TestMergeFlagsIntoCmArgs_EnvSource(t *testing.T) {
envFileName := "env1"
envFileName2 := "env2"
ds := &types.DataSources{}
err := mergeData(ds, dataConfig{EnvFileSource: envFileName})
err := mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{EnvFileSource: envFileName})
if err != nil {
t.Fatalf("Merge initial env source should not return error")
}
@@ -122,7 +122,7 @@ func TestMergeData_EnvSource(t *testing.T) {
t.Fatalf("Initial env source filename should have been added")
}
err = mergeData(ds, dataConfig{EnvFileSource: envFileName2})
err = mergeFlagsIntoCmArgs(ds, cMapFlagsAndArgs{EnvFileSource: envFileName2})
if err == nil {
t.Fatalf("Updating env source should return an error")
}

View File

@@ -66,9 +66,9 @@ func (o *diffOptions) Validate(args []string) error {
}
// RunDiff gets the differences between Application.MakeCustomizedResMap() and Application.MakeUncustomizedResMap().
func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error {
func (o *diffOptions) RunDiff(out, errOut io.Writer, fSys fs.FileSystem) error {
l := loader.Init([]loader.SchemeLoader{loader.NewFileLoader(fs)})
l := loader.NewLoader(loader.NewFileLoader(fSys))
absPath, err := filepath.Abs(o.kustomizationPath)
if err != nil {
@@ -80,7 +80,7 @@ func (o *diffOptions) RunDiff(out, errOut io.Writer, fs fs.FileSystem) error {
return err
}
application, err := app.NewApplication(rootLoader)
application, err := app.NewApplication(rootLoader, fSys)
if err != nil {
return err
}

View File

@@ -44,11 +44,12 @@ func TestDiff(t *testing.T) {
const updateEnvVar = "UPDATE_KUSTOMIZE_EXPECTED_DATA"
updateKustomizeExpected := os.Getenv(updateEnvVar) == "true"
noopDir, _ := regexp.Compile(`/tmp/noop-[0-9]*/`)
transformedDir, _ := regexp.Compile(`/tmp/transformed-[0-9]*/`)
tempDir := regexp.QuoteMeta(filepath.Clean(os.TempDir()))
noopDir, _ := regexp.Compile(tempDir + `/noop-[0-9]*/`)
transformedDir, _ := regexp.Compile(tempDir + `/transformed-[0-9]*/`)
timestamp, _ := regexp.Compile(`[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9].[0-9]* [+-]{1}[0-9]{4}`)
fs := fs.MakeRealFS()
fSys := fs.MakeRealFS()
testcases := sets.NewString()
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
@@ -74,7 +75,7 @@ func TestDiff(t *testing.T) {
for _, testcaseName := range testcases.List() {
t.Run(testcaseName, func(t *testing.T) {
runDiffTestCase(t, testcaseName, updateKustomizeExpected, fs,
runDiffTestCase(t, testcaseName, updateKustomizeExpected, fSys,
noopDir, transformedDir, timestamp)
})
}

View File

@@ -45,25 +45,23 @@ func newKustomizationFile(mPath string, fsys fs.FileSystem) (*kustomizationFile,
}
func (mf *kustomizationFile) validate() error {
f, err := mf.fsys.Stat(mf.path)
if err != nil {
if !mf.fsys.Exists(mf.path) {
errorMsg := fmt.Sprintf("Missing kustomization file '%s'.\n", mf.path)
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
return merr
}
if f.IsDir() {
if mf.fsys.IsDir(mf.path) {
mf.path = path.Join(mf.path, constants.KustomizationFileName)
_, err = mf.fsys.Stat(mf.path)
if err != nil {
if !mf.fsys.Exists(mf.path) {
errorMsg := fmt.Sprintf("Missing kustomization file '%s'.\n", mf.path)
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
return merr
}
} else {
if !strings.HasSuffix(mf.path, constants.KustomizationFileName) {
errorMsg := fmt.Sprintf("Kustomization file path (%s) should have %s suffix\n", mf.path, constants.KustomizationFileSuffix)
merr := interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
return merr
errorMsg := fmt.Sprintf("Kustomization file path (%s) should have %s suffix\n",
mf.path, constants.KustomizationFileSuffix)
return interror.KustomizationError{KustomizationPath: mf.path, ErrorMsg: errorMsg}
}
}
return nil
@@ -84,7 +82,7 @@ func (mf *kustomizationFile) read() (*types.Kustomization, error) {
func (mf *kustomizationFile) write(kustomization *types.Kustomization) error {
if kustomization == nil {
return errors.New("util: kustomization file arg is nil.")
return errors.New("util: kustomization file arg is nil")
}
bytes, err := yaml.Marshal(kustomization)
if err != nil {

View File

@@ -62,7 +62,7 @@ func TestEmptyFile(t *testing.T) {
func TestNewNotExist(t *testing.T) {
badSuffix := "foo.bar"
fakeFS := fs.MakeFakeFS()
fakeFS.Mkdir(".", 0644)
fakeFS.Mkdir(".")
fakeFS.Create(badSuffix)
_, err := newKustomizationFile(constants.KustomizationFileName, fakeFS)
if err == nil {

111
pkg/commands/setimagetag.go Normal file
View File

@@ -0,0 +1,111 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"errors"
"strings"
"github.com/spf13/cobra"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/types"
"sort"
)
type setImageTagOptions struct {
imageTagMap map[string]string
}
// newCmdSetImageTag sets the new tags for images in the kustomization.
func newCmdSetImageTag(fsys fs.FileSystem) *cobra.Command {
var o setImageTagOptions
cmd := &cobra.Command{
Use: "imagetag",
Short: "Sets images and their new tags in the kustomization file",
Example: `
The command
set imagetag nginx:1.8.0 my-app:latest
will add
imageTags:
- name: nginx
newTag: 1.8.0
- name: my-app
newTag: latest
to the kustomization file if it doesn't exist,
and overwrite the previous newTag if the image name exists.
`,
RunE: func(cmd *cobra.Command, args []string) error {
err := o.Validate(args)
if err != nil {
return err
}
return o.RunSetImageTags(fsys)
},
}
return cmd
}
// Validate validates setImageTag command.
func (o *setImageTagOptions) Validate(args []string) error {
if len(args) == 0 {
return errors.New("No image and newTag specified.")
}
o.imageTagMap = make(map[string]string)
for _, arg := range args {
imagetag := strings.Split(arg, ":")
if len(imagetag) != 2 {
return errors.New("Invalid format of imagetag, must specify it as <image>:<newtag>")
}
o.imageTagMap[imagetag[0]] = imagetag[1]
}
return nil
}
// RunSetImageTags runs setImageTags command (does real work).
func (o *setImageTagOptions) RunSetImageTags(fsys fs.FileSystem) error {
mf, err := newKustomizationFile(constants.KustomizationFileName, fsys)
if err != nil {
return err
}
m, err := mf.read()
if err != nil {
return err
}
imageTagMap := map[string]string{}
for _, it := range m.ImageTags {
imageTagMap[it.Name] = it.NewTag
}
for key, value := range o.imageTagMap {
imageTagMap[key] = value
}
var imageTags []types.ImageTag
for key, value := range imageTagMap {
imageTags = append(imageTags, types.ImageTag{Name: key, NewTag: value})
}
sort.Slice(imageTags, func(i, j int) bool {
return imageTags[i].Name < imageTags[j].Name
})
m.ImageTags = imageTags
return mf.write(m)
}

View File

@@ -0,0 +1,97 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package commands
import (
"strings"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
)
func TestSetImageTagsHappyPath(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdSetImageTag(fakeFS)
args := []string{"image1:tag1", "image2:tag2"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
expected := []byte(`
imageTags:
- name: image1
newTag: tag1
- name: image2
newTag: tag2
`)
if !strings.Contains(string(content), string(expected)) {
t.Errorf("expected imageTags in kustomization file")
}
}
func TestSetImageTagsOverride(t *testing.T) {
fakeFS := fs.MakeFakeFS()
fakeFS.WriteFile(constants.KustomizationFileName, []byte(kustomizationContent))
cmd := newCmdSetImageTag(fakeFS)
args := []string{"image1:tag1", "image2:tag1"}
err := cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
args = []string{"image2:tag2", "image3:tag3"}
err = cmd.RunE(cmd, args)
if err != nil {
t.Errorf("unexpected cmd error: %v", err)
}
content, err := fakeFS.ReadFile(constants.KustomizationFileName)
if err != nil {
t.Errorf("unexpected read error: %v", err)
}
expected := []byte(`
imageTags:
- name: image1
newTag: tag1
- name: image2
newTag: tag2
- name: image3
newTag: tag3
`)
if !strings.Contains(string(content), string(expected)) {
t.Errorf("expected imageTags in kustomization file %s", string(content))
}
}
func TestSetImageTagsNoArgs(t *testing.T) {
fakeFS := fs.MakeFakeFS()
cmd := newCmdSetImageTag(fakeFS)
err := cmd.Execute()
if err == nil {
t.Errorf("expected error: %v", err)
}
if err.Error() != "No image and newTag specified." {
t.Errorf("incorrect error: %v", err.Error())
}
}

View File

@@ -32,6 +32,33 @@ diff -u -N /tmp/noop/apps_v1beta2_Deployment_nginx.yaml /tmp/transformed/apps_v1
spec:
containers:
- image: nginx
diff -u -N /tmp/noop/networking.k8s.io_v1_NetworkPolicy_nginx.yaml /tmp/transformed/networking.k8s.io_v1_NetworkPolicy_nginx.yaml
--- /tmp/noop/networking.k8s.io_v1_NetworkPolicy_nginx.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/networking.k8s.io_v1_NetworkPolicy_nginx.yaml YYYY-MM-DD HH:MM:SS
@@ -1,13 +1,21 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
- name: nginx
+ annotations:
+ note: This is a test annotation
+ labels:
+ app: mynginx
+ org: example.com
+ team: foo
+ name: team-foo-nginx
spec:
ingress:
- from:
- podSelector:
matchLabels:
- app: nginx
+ app: mynginx
+ org: example.com
+ team: foo
podSelector:
matchExpressions:
- key: app
diff -u -N /tmp/noop/v1_Service_nginx.yaml /tmp/transformed/v1_Service_nginx.yaml
--- /tmp/noop/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1_Service_nginx.yaml YYYY-MM-DD HH:MM:SS

View File

@@ -44,3 +44,28 @@ spec:
containers:
- image: nginx
name: nginx
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
annotations:
note: This is a test annotation
labels:
app: mynginx
org: example.com
team: foo
name: team-foo-nginx
spec:
ingress:
- from:
- podSelector:
matchLabels:
app: mynginx
org: example.com
team: foo
podSelector:
matchExpressions:
- key: app
operator: In
values:
- test

View File

@@ -0,0 +1,13 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: nginx
spec:
podSelector:
matchExpressions:
- {key: app, operator: In, values: [test]}
ingress:
- from:
- podSelector:
matchLabels:
app: nginx

View File

@@ -12,8 +12,8 @@ diff -u -N /tmp/noop/jingfang.example.com_v1beta1_MyKind_mykind.yaml /tmp/transf
- name: bee
+ name: test-bee
secretRef:
- name: crdsecret-m5ht5thcb4
+ name: test-crdsecret-m48btmkck5
- name: crdsecret
+ name: test-crdsecret
diff -u -N /tmp/noop/v1beta1_Bee_bee.yaml /tmp/transformed/v1beta1_Bee_bee.yaml
--- /tmp/noop/v1beta1_Bee_bee.yaml YYYY-MM-DD HH:MM:SS
+++ /tmp/transformed/v1beta1_Bee_bee.yaml YYYY-MM-DD HH:MM:SS
@@ -32,5 +32,5 @@ diff -u -N /tmp/noop/v1_Secret_crdsecret.yaml /tmp/transformed/v1_Secret_crdsecr
PATH: YmJiYmJiYmIK
kind: Secret
metadata:
- name: crdsecret-m5ht5thcb4
+ name: test-crdsecret-m48btmkck5
- name: crdsecret
+ name: test-crdsecret

View File

@@ -3,7 +3,7 @@ data:
PATH: YmJiYmJiYmIK
kind: Secret
metadata:
name: test-crdsecret-m48btmkck5
name: test-crdsecret
---
apiVersion: v1beta1
kind: Bee
@@ -20,4 +20,4 @@ spec:
beeRef:
name: test-bee
secretRef:
name: test-crdsecret-m48btmkck5
name: test-crdsecret

View File

@@ -48,7 +48,7 @@ diff -u -N /tmp/noop/extensions_v1beta1_Deployment_mungebot.yaml /tmp/transforme
- name: foo
value: bar
- image: nginx
+ image: nginx:1.7.9
+ image: nginx:1.8.0
name: nginx
ports:
- containerPort: 80

View File

@@ -111,7 +111,7 @@ spec:
name: test-infra-app-tls-6hkmhf2224
- name: foo
value: bar
image: nginx:1.7.9
image: nginx:1.8.0
name: nginx
ports:
- containerPort: 80

View File

@@ -1,136 +0,0 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package configmapandsecret generates configmaps and secrets per generator rules.
package configmapandsecret
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"time"
cutil "github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret/util"
"github.com/kubernetes-sigs/kustomize/pkg/hash"
"github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
// MakeConfigmapAndGenerateName makes a configmap and returns the configmap and the name appended with a hash.
func MakeConfigmapAndGenerateName(cm types.ConfigMapArgs) (*unstructured.Unstructured, string, error) {
corev1CM, err := makeConfigMap(cm)
if err != nil {
return nil, "", err
}
h, err := hash.ConfigMapHash(corev1CM)
if err != nil {
return nil, "", err
}
nameWithHash := fmt.Sprintf("%s-%s", corev1CM.GetName(), h)
unstructuredCM, err := objectToUnstructured(corev1CM)
return unstructuredCM, nameWithHash, err
}
// MakeSecretAndGenerateName returns a secret with the name appended with a hash.
func MakeSecretAndGenerateName(secret types.SecretArgs, path string) (*unstructured.Unstructured, string, error) {
corev1Secret, err := makeSecret(secret, path)
if err != nil {
return nil, "", err
}
h, err := hash.SecretHash(corev1Secret)
if err != nil {
return nil, "", err
}
nameWithHash := fmt.Sprintf("%s-%s", secret.Name, h)
unstructuredCM, err := objectToUnstructured(corev1Secret)
return unstructuredCM, nameWithHash, err
}
func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error) {
marshaled, err := json.Marshal(in)
if err != nil {
return nil, err
}
var out unstructured.Unstructured
err = out.UnmarshalJSON(marshaled)
return &out, err
}
func makeConfigMap(cm types.ConfigMapArgs) (*corev1.ConfigMap, error) {
corev1cm := &corev1.ConfigMap{}
corev1cm.APIVersion = "v1"
corev1cm.Kind = "ConfigMap"
corev1cm.Name = cm.Name
corev1cm.Data = map[string]string{}
if cm.EnvSource != "" {
if err := cutil.HandleConfigMapFromEnvFileSource(corev1cm, cm.EnvSource); err != nil {
return nil, err
}
}
if cm.FileSources != nil {
if err := cutil.HandleConfigMapFromFileSources(corev1cm, cm.FileSources); err != nil {
return nil, err
}
}
if cm.LiteralSources != nil {
if err := cutil.HandleConfigMapFromLiteralSources(corev1cm, cm.LiteralSources); err != nil {
return nil, err
}
}
return corev1cm, nil
}
func makeSecret(secret types.SecretArgs, path string) (*corev1.Secret, error) {
corev1secret := &corev1.Secret{}
corev1secret.APIVersion = "v1"
corev1secret.Kind = "Secret"
corev1secret.Name = secret.Name
corev1secret.Type = corev1.SecretType(secret.Type)
if corev1secret.Type == "" {
corev1secret.Type = corev1.SecretTypeOpaque
}
corev1secret.Data = map[string][]byte{}
for k, v := range secret.Commands {
out, err := createSecretKey(path, v)
if err != nil {
return nil, err
}
corev1secret.Data[k] = out
}
return corev1secret, nil
}
func createSecretKey(wd string, command string) ([]byte, error) {
fi, err := os.Stat(wd)
if err != nil || !fi.IsDir() {
wd = filepath.Dir(wd)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "sh", "-c", command)
cmd.Dir = wd
return cmd.Output()
}

View File

@@ -0,0 +1,217 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package configmapandsecret generates configmaps and secrets per generator rules.
package configmapandsecret
import (
"encoding/json"
"fmt"
"path"
"strings"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/hash"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/types"
"github.com/pkg/errors"
"k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
)
// ConfigMapFactory makes ConfigMaps.
type ConfigMapFactory struct {
fSys fs.FileSystem
ldr loader.Loader
}
// NewConfigMapFactory returns a new ConfigMapFactory.
func NewConfigMapFactory(
fSys fs.FileSystem, l loader.Loader) *ConfigMapFactory {
return &ConfigMapFactory{fSys: fSys, ldr: l}
}
// MakeUnstructAndGenerateName returns an configmap and the name appended with a hash.
func (f *ConfigMapFactory) MakeUnstructAndGenerateName(
args *types.ConfigMapArgs) (*unstructured.Unstructured, string, error) {
cm, err := f.MakeConfigMap(args)
if err != nil {
return nil, "", err
}
h, err := hash.ConfigMapHash(cm)
if err != nil {
return nil, "", err
}
nameWithHash := fmt.Sprintf("%s-%s", cm.GetName(), h)
unstructuredCM, err := objectToUnstructured(cm)
return unstructuredCM, nameWithHash, err
}
func objectToUnstructured(in runtime.Object) (*unstructured.Unstructured, error) {
marshaled, err := json.Marshal(in)
if err != nil {
return nil, err
}
var out unstructured.Unstructured
err = out.UnmarshalJSON(marshaled)
return &out, err
}
func (f *ConfigMapFactory) makeFreshConfigMap(
args *types.ConfigMapArgs) *corev1.ConfigMap {
cm := &corev1.ConfigMap{}
cm.APIVersion = "v1"
cm.Kind = "ConfigMap"
cm.Name = args.Name
cm.Data = map[string]string{}
return cm
}
// MakeConfigMap returns a new ConfigMap, or nil and an error.
func (f *ConfigMapFactory) MakeConfigMap(
args *types.ConfigMapArgs) (*corev1.ConfigMap, error) {
var all []kvPair
var err error
cm := f.makeFreshConfigMap(args)
pairs, err := keyValuesFromEnvFile(f.ldr, args.EnvSource)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"env source file: %s",
args.EnvSource))
}
all = append(all, pairs...)
pairs, err = keyValuesFromLiteralSources(args.LiteralSources)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"literal sources %v", args.LiteralSources))
}
all = append(all, pairs...)
pairs, err = keyValuesFromFileSources(f.ldr, args.FileSources)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf(
"file sources: %v", args.FileSources))
}
all = append(all, pairs...)
for _, kv := range all {
err = addKvToConfigMap(cm, kv.key, kv.value)
if err != nil {
return nil, err
}
}
return cm, nil
}
func keyValuesFromLiteralSources(sources []string) ([]kvPair, error) {
var kvs []kvPair
for _, s := range sources {
k, v, err := parseLiteralSource(s)
if err != nil {
return nil, err
}
kvs = append(kvs, kvPair{key: k, value: v})
}
return kvs, nil
}
func keyValuesFromFileSources(ldr loader.Loader, sources []string) ([]kvPair, error) {
var kvs []kvPair
for _, s := range sources {
k, fPath, err := parseFileSource(s)
if err != nil {
return nil, err
}
content, err := ldr.Load(fPath)
if err != nil {
return nil, err
}
kvs = append(kvs, kvPair{key: k, value: string(content)})
}
return kvs, nil
}
func keyValuesFromEnvFile(l loader.Loader, path string) ([]kvPair, error) {
if path == "" {
return nil, nil
}
content, err := l.Load(path)
if err != nil {
return nil, err
}
return keyValuesFromLines(content)
}
// addKvToConfigMap adds the given key and data to the given config map.
// Error if key invalid, or already exists.
func addKvToConfigMap(configMap *v1.ConfigMap, keyName, data string) error {
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ";"))
}
if _, entryExists := configMap.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v", keyName, configMap.Data)
}
configMap.Data[keyName] = data
return nil
}
// parseFileSource parses the source given.
//
// Acceptable formats include:
// 1. source-path: the basename will become the key name
// 2. source-name=source-path: the source-name will become the key name and
// source-path is the path to the key file.
//
// Key names cannot include '='.
func parseFileSource(source string) (keyName, filePath string, err error) {
numSeparators := strings.Count(source, "=")
switch {
case numSeparators == 0:
return path.Base(source), source, nil
case numSeparators == 1 && strings.HasPrefix(source, "="):
return "", "", fmt.Errorf("key name for file path %v missing", strings.TrimPrefix(source, "="))
case numSeparators == 1 && strings.HasSuffix(source, "="):
return "", "", fmt.Errorf("file path for key name %v missing", strings.TrimSuffix(source, "="))
case numSeparators > 1:
return "", "", errors.New("key names or file paths cannot contain '='")
default:
components := strings.Split(source, "=")
return components[0], components[1], nil
}
}
// parseLiteralSource parses the source key=val pair into its component pieces.
// This functionality is distinguished from strings.SplitN(source, "=", 2) since
// it returns an error in the case of empty keys, values, or a missing equals sign.
func parseLiteralSource(source string) (keyName, value string, err error) {
// leading equal is invalid
if strings.Index(source, "=") == 0 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
// split after the first equal (so values can have the = character)
items := strings.SplitN(source, "=", 2)
if len(items) != 2 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
return items[0], items[1], nil
}

View File

@@ -17,10 +17,11 @@ limitations under the License.
package configmapandsecret
import (
"encoding/base64"
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -93,41 +94,6 @@ func makeLiteralConfigMap(name string) *corev1.ConfigMap {
}
}
func makeTestSecret(name string) *corev1.Secret {
return &corev1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Data: map[string][]byte{
"DB_USERNAME": []byte("admin"),
"DB_PASSWORD": []byte("somepw"),
},
Type: corev1.SecretTypeOpaque,
}
}
func makeUnstructuredSecret(name string) *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "v1",
"kind": "Secret",
"metadata": map[string]interface{}{
"name": name,
"creationTimestamp": nil,
},
"type": string(corev1.SecretTypeOpaque),
"data": map[string]interface{}{
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
},
},
}
}
func TestConstructConfigMap(t *testing.T) {
type testCase struct {
description string
@@ -168,8 +134,12 @@ func TestConstructConfigMap(t *testing.T) {
},
}
// TODO: all tests should use a FakeFs
fSys := fs.MakeRealFS()
f := NewConfigMapFactory(fSys,
loader.NewLoader(loader.NewFileLoader(fSys)))
for _, tc := range testCases {
cm, err := makeConfigMap(tc.input)
cm, err := f.MakeConfigMap(&tc.input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
@@ -179,39 +149,6 @@ func TestConstructConfigMap(t *testing.T) {
}
}
func TestConstructSecret(t *testing.T) {
secret := types.SecretArgs{
Name: "secret",
Commands: map[string]string{
"DB_USERNAME": "printf admin",
"DB_PASSWORD": "printf somepw",
},
Type: "Opaque",
}
cm, err := makeSecret(secret, ".")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected := makeTestSecret("secret")
if !reflect.DeepEqual(*cm, *expected) {
t.Fatalf("%#v\ndoesn't match expected:\n%#v", *cm, *expected)
}
}
func TestFailConstructSecret(t *testing.T) {
secret := types.SecretArgs{
Name: "secret",
Commands: map[string]string{
"FAILURE": "false", // This will fail.
},
Type: "Opaque",
}
_, err := makeSecret(secret, ".")
if err == nil {
t.Fatalf("Expected failure.")
}
}
func TestObjectConvertToUnstructured(t *testing.T) {
type testCase struct {
description string

View File

@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package resmap
package configmapandsecret
import (
"bufio"
@@ -28,14 +28,14 @@ import (
"k8s.io/apimachinery/pkg/util/validation"
)
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
// kvPair represents a key value pair.
type kvPair struct {
key string
value string
}
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
// keyValuesFromLines parses given content in to a list of key-value pairs.
func keyValuesFromLines(content []byte) ([]kvPair, error) {
var kvs []kvPair

View File

@@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resmap
package configmapandsecret
import (
"reflect"

View File

@@ -0,0 +1,60 @@
package configmapandsecret
import (
"context"
"os/exec"
"path/filepath"
"time"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/types"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
)
// SecretFactory makes Secrets.
type SecretFactory struct {
fSys fs.FileSystem
wd string
}
// NewSecretFactory returns a new SecretFactory.
func NewSecretFactory(fSys fs.FileSystem, wd string) *SecretFactory {
return &SecretFactory{fSys: fSys, wd: wd}
}
// MakeSecret returns a new secret.
func (f *SecretFactory) MakeSecret(args types.SecretArgs) (*corev1.Secret, error) {
s := &corev1.Secret{}
s.APIVersion = "v1"
s.Kind = "Secret"
s.Name = args.Name
s.Type = corev1.SecretType(args.Type)
if s.Type == "" {
s.Type = corev1.SecretTypeOpaque
}
s.Data = map[string][]byte{}
for k, v := range args.Commands {
out, err := f.createSecretKey(v)
if err != nil {
return nil, errors.Wrap(err, "createSecretKey")
}
s.Data[k] = out
}
return s, nil
}
// Run a command, return its output as the secret.
func (f *SecretFactory) createSecretKey(command string) ([]byte, error) {
if !f.fSys.IsDir(f.wd) {
f.wd = filepath.Dir(f.wd)
if !f.fSys.IsDir(f.wd) {
return nil, errors.New("not a directory: " + f.wd)
}
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "sh", "-c", command)
cmd.Dir = f.wd
return cmd.Output()
}

View File

@@ -1,134 +0,0 @@
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
)
// HandleConfigMapFromLiteralSources adds the specified literal source
// information into the provided configMap.
func HandleConfigMapFromLiteralSources(configMap *v1.ConfigMap, literalSources []string) error {
for _, literalSource := range literalSources {
keyName, value, err := ParseLiteralSource(literalSource)
if err != nil {
return err
}
err = addKeyFromLiteralToConfigMap(configMap, keyName, value)
if err != nil {
return err
}
}
return nil
}
// HandleConfigMapFromFileSources adds the specified file source information
// into the provided configMap
func HandleConfigMapFromFileSources(configMap *v1.ConfigMap, fileSources []string) error {
for _, fileSource := range fileSources {
keyName, filePath, err := ParseFileSource(fileSource)
if err != nil {
return err
}
info, err := os.Stat(filePath)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
default:
return fmt.Errorf("error reading %s: %v", filePath, err)
}
}
if info.IsDir() {
if strings.Contains(fileSource, "=") {
return fmt.Errorf("cannot give a key name for a directory path.")
}
fileList, err := ioutil.ReadDir(filePath)
if err != nil {
return fmt.Errorf("error listing files in %s: %v", filePath, err)
}
for _, item := range fileList {
itemPath := path.Join(filePath, item.Name())
if item.Mode().IsRegular() {
keyName = item.Name()
err = addKeyFromFileToConfigMap(configMap, keyName, itemPath)
if err != nil {
return err
}
}
}
} else {
if err := addKeyFromFileToConfigMap(configMap, keyName, filePath); err != nil {
return err
}
}
}
return nil
}
// HandleConfigMapFromEnvFileSource adds the specified env file source information
// into the provided configMap
func HandleConfigMapFromEnvFileSource(configMap *v1.ConfigMap, envFileSource string) error {
info, err := os.Stat(envFileSource)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
default:
return fmt.Errorf("error reading %s: %v", envFileSource, err)
}
}
if info.IsDir() {
return fmt.Errorf("env config file cannot be a directory")
}
return addFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToConfigMap(configMap, key, value)
})
}
// addKeyFromFileToConfigMap adds a key with the given name to a ConfigMap, populating
// the value with the content of the given file path, or returns an error.
func addKeyFromFileToConfigMap(configMap *v1.ConfigMap, keyName, filePath string) error {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
return addKeyFromLiteralToConfigMap(configMap, keyName, string(data))
}
// addKeyFromLiteralToConfigMap adds the given key and data to the given config map,
// returning an error if the key is not valid or if the key already exists.
func addKeyFromLiteralToConfigMap(configMap *v1.ConfigMap, keyName, data string) error {
// Note, the rules for ConfigMap keys are the exact same as the ones for SecretKeys.
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a ConfigMap: %s", keyName, strings.Join(errs, ";"))
}
if _, entryExists := configMap.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v.", keyName, configMap.Data)
}
configMap.Data[keyName] = data
return nil
}

View File

@@ -1,103 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"bufio"
"bytes"
"fmt"
"os"
"strings"
"unicode"
"unicode/utf8"
"k8s.io/apimachinery/pkg/util/validation"
)
var utf8bom = []byte{0xEF, 0xBB, 0xBF}
// processEnvFileLine returns a blank key if the line is empty or a comment.
// The value will be retrieved from the environment if necessary.
func processEnvFileLine(line []byte, filePath string,
currentLine int) (key, value string, err error) {
if !utf8.Valid(line) {
return ``, ``, fmt.Errorf("env file %s contains invalid utf8 bytes at line %d: %v",
filePath, currentLine+1, line)
}
// We trim UTF8 BOM from the first line of the file but no others
if currentLine == 0 {
line = bytes.TrimPrefix(line, utf8bom)
}
// trim the line from all leading whitespace first
line = bytes.TrimLeftFunc(line, unicode.IsSpace)
// If the line is empty or a comment, we return a blank key/value pair.
if len(line) == 0 || line[0] == '#' {
return ``, ``, nil
}
data := strings.SplitN(string(line), "=", 2)
key = data[0]
if errs := validation.IsEnvVarName(key); len(errs) != 0 {
return ``, ``, fmt.Errorf("%q is not a valid key name: %s", key, strings.Join(errs, ";"))
}
if len(data) == 2 {
value = data[1]
} else {
// No value (no `=` in the line) is a signal to obtain the value
// from the environment.
value = os.Getenv(key)
}
return key, value, err
}
// addFromEnvFile processes an env file allows a generic addTo to handle the
// collection of key value pairs or returns an error.
func addFromEnvFile(filePath string, addTo func(key, value string) error) error {
f, err := os.Open(filePath)
if err != nil {
return err
}
defer f.Close()
scanner := bufio.NewScanner(f)
currentLine := 0
for scanner.Scan() {
// Process the current line, retrieving a key/value pair if
// possible.
scannedBytes := scanner.Bytes()
key, value, err := processEnvFileLine(scannedBytes, filePath, currentLine)
if err != nil {
return err
}
currentLine++
if len(key) == 0 {
// no key means line was empty or a comment
continue
}
if err = addTo(key, value); err != nil {
return err
}
}
return nil
}

View File

@@ -1,126 +0,0 @@
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"io/ioutil"
"os"
"path"
"strings"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
)
// HandleFromLiteralSources adds the specified literal source information into the provided secret
func HandleFromLiteralSources(secret *v1.Secret, literalSources []string) error {
for _, literalSource := range literalSources {
keyName, value, err := ParseLiteralSource(literalSource)
if err != nil {
return err
}
if err = addKeyFromLiteralToSecret(secret, keyName, []byte(value)); err != nil {
return err
}
}
return nil
}
// HandleFromFileSources adds the specified file source information into the provided secret
func HandleFromFileSources(secret *v1.Secret, fileSources []string) error {
for _, fileSource := range fileSources {
keyName, filePath, err := ParseFileSource(fileSource)
if err != nil {
return err
}
info, err := os.Stat(filePath)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", filePath, err.Err)
default:
return fmt.Errorf("error reading %s: %v", filePath, err)
}
}
if info.IsDir() {
if strings.Contains(fileSource, "=") {
return fmt.Errorf("cannot give a key name for a directory path.")
}
fileList, err := ioutil.ReadDir(filePath)
if err != nil {
return fmt.Errorf("error listing files in %s: %v", filePath, err)
}
for _, item := range fileList {
itemPath := path.Join(filePath, item.Name())
if item.Mode().IsRegular() {
keyName = item.Name()
if err = addKeyFromFileToSecret(secret, keyName, itemPath); err != nil {
return err
}
}
}
} else {
if err := addKeyFromFileToSecret(secret, keyName, filePath); err != nil {
return err
}
}
}
return nil
}
// HandleFromEnvFileSource adds the specified env file source information
// into the provided secret
func HandleFromEnvFileSource(secret *v1.Secret, envFileSource string) error {
info, err := os.Stat(envFileSource)
if err != nil {
switch err := err.(type) {
case *os.PathError:
return fmt.Errorf("error reading %s: %v", envFileSource, err.Err)
default:
return fmt.Errorf("error reading %s: %v", envFileSource, err)
}
}
if info.IsDir() {
return fmt.Errorf("env secret file cannot be a directory")
}
return addFromEnvFile(envFileSource, func(key, value string) error {
return addKeyFromLiteralToSecret(secret, key, []byte(value))
})
}
func addKeyFromFileToSecret(secret *v1.Secret, keyName, filePath string) error {
data, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
return addKeyFromLiteralToSecret(secret, keyName, data)
}
func addKeyFromLiteralToSecret(secret *v1.Secret, keyName string, data []byte) error {
if errs := validation.IsConfigMapKey(keyName); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name for a Secret: %s", keyName, strings.Join(errs, ";"))
}
if _, entryExists := secret.Data[keyName]; entryExists {
return fmt.Errorf("cannot add key %s, another key by that name already exists: %v.", keyName, secret.Data)
}
secret.Data[keyName] = data
return nil
}

View File

@@ -1,93 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package util offers Configmap and Secret generation utilities.
package util
import (
"crypto/sha256"
"errors"
"fmt"
"path"
"strings"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
)
// ParseRFC3339 parses an RFC3339 date in either RFC3339Nano or RFC3339 format.
func ParseRFC3339(s string) (metav1.Time, error) {
if t, timeErr := time.Parse(time.RFC3339Nano, s); timeErr == nil {
return metav1.Time{Time: t}, nil
}
t, err := time.Parse(time.RFC3339, s)
if err != nil {
return metav1.Time{}, err
}
return metav1.Time{Time: t}, nil
}
// HashObject encodes object using given codec and returns MD5 sum of the result.
func HashObject(obj runtime.Object, codec runtime.Encoder) (string, error) {
data, err := runtime.Encode(codec, obj)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", sha256.Sum256(data)), nil
}
// ParseFileSource parses the source given.
//
// Acceptable formats include:
// 1. source-path: the basename will become the key name
// 2. source-name=source-path: the source-name will become the key name and
// source-path is the path to the key file.
//
// Key names cannot include '='.
func ParseFileSource(source string) (keyName, filePath string, err error) {
numSeparators := strings.Count(source, "=")
switch {
case numSeparators == 0:
return path.Base(source), source, nil
case numSeparators == 1 && strings.HasPrefix(source, "="):
return "", "", fmt.Errorf("key name for file path %v missing.", strings.TrimPrefix(source, "="))
case numSeparators == 1 && strings.HasSuffix(source, "="):
return "", "", fmt.Errorf("file path for key name %v missing.", strings.TrimSuffix(source, "="))
case numSeparators > 1:
return "", "", errors.New("Key names or file paths cannot contain '='.")
default:
components := strings.Split(source, "=")
return components[0], components[1], nil
}
}
// ParseLiteralSource parses the source key=val pair into its component pieces.
// This functionality is distinguished from strings.SplitN(source, "=", 2) since
// it returns an error in the case of empty keys, values, or a missing equals sign.
func ParseLiteralSource(source string) (keyName, value string, err error) {
// leading equal is invalid
if strings.Index(source, "=") == 0 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
// split after the first equal (so values can have the = character)
items := strings.SplitN(source, "=", 2)
if len(items) != 2 {
return "", "", fmt.Errorf("invalid literal source %v, expected key=value", source)
}
return items[0], items[1], nil
}

View File

@@ -66,7 +66,7 @@ func (p *pathConfigs) addPrefixPathConfig(config transformers.PathConfig) {
// RegisterCRDs parse CRD schemas from paths and update various pathConfigs
func RegisterCRDs(loader loader.Loader, paths []string) error {
pathConfigs := []pathConfigs{}
var pathConfigs []pathConfigs
for _, path := range paths {
pathConfig, err := registerCRD(loader, path)
if err != nil {
@@ -80,8 +80,7 @@ func RegisterCRDs(loader loader.Loader, paths []string) error {
// register CRD from one path
func registerCRD(loader loader.Loader, path string) ([]pathConfigs, error) {
result := []pathConfigs{}
var result []pathConfigs
content, err := loader.Load(path)
if err != nil {
return result, err

View File

@@ -142,12 +142,12 @@ If it is not set we generate a secret dynamically",
)
func makeLoader(t *testing.T) loader.Loader {
loader := loadertest.NewFakeLoader("/testpath")
err := loader.AddFile("/testpath/crd.json", []byte(crdContent))
ldr := loadertest.NewFakeLoader("/testpath")
err := ldr.AddFile("/testpath/crd.json", []byte(crdContent))
if err != nil {
t.Fatalf("Failed to setup fake loader.")
t.Fatalf("Failed to setup fake ldr.")
}
return loader
return ldr
}
func TestRegisterCRD(t *testing.T) {
@@ -184,9 +184,9 @@ func TestRegisterCRD(t *testing.T) {
},
}
loader := makeLoader(t)
ldr := makeLoader(t)
pathconfig, _ := registerCRD(loader, "/testpath/crd.json")
pathconfig, _ := registerCRD(ldr, "/testpath/crd.json")
sort.Slice(pathconfig[0].namereferencePathConfigs, func(i, j int) bool {
return pathconfig[0].namereferencePathConfigs[i].GVK() < pathconfig[0].namereferencePathConfigs[j].GVK()

View File

@@ -60,7 +60,7 @@ func writeYamlToNewDir(in resmap.ResMap, prefix string) (*directory, error) {
if err != nil {
return nil, err
}
err = print(obj, f)
err = write(obj, f)
f.Close()
if err != nil {
return nil, err
@@ -69,8 +69,8 @@ func writeYamlToNewDir(in resmap.ResMap, prefix string) (*directory, error) {
return dir, nil
}
// Print the object as YAML.
func print(obj interface{}, w io.Writer) error {
// Write the object as YAML.
func write(obj interface{}, w io.Writer) error {
if obj == nil {
return nil
}

View File

@@ -21,3 +21,6 @@ secretGenerator:
tls.crt: "cat secret/tls.cert"
tls.key: "cat secret/tls.key"
type: "kubernetes.io/tls"
imageTags:
- name: nginx
newTag: 1.8.0

View File

@@ -24,9 +24,7 @@ import (
)
func ExampleNew() {
exec := exec.New()
cmd := exec.Command("echo", "Bonjour!")
cmd := exec.New().Command("echo", "Bonjour!")
buff := bytes.Buffer{}
cmd.SetStdout(&buff)
if err := cmd.Run(); err != nil {

View File

@@ -114,6 +114,6 @@ func tryReadVariableName(input string) (string, bool, int) {
// 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
return string(operator) + string(input[0]), false, 1
}
}

View File

@@ -18,7 +18,6 @@ package fs
import (
"fmt"
"os"
)
var _ FileSystem = &FakeFS{}
@@ -42,7 +41,7 @@ func (fs *FakeFS) Create(name string) (File, error) {
}
// Mkdir assures a fake directory appears in the in-memory file system.
func (fs *FakeFS) Mkdir(name string, perm os.FileMode) error {
func (fs *FakeFS) Mkdir(name string) error {
fs.m[name] = makeDir(name)
return nil
}
@@ -55,12 +54,19 @@ func (fs *FakeFS) Open(name string) (File, error) {
return fs.m[name], nil
}
// Stat always returns nil FileInfo, and returns an error if file does not exist.
func (fs *FakeFS) Stat(name string) (os.FileInfo, error) {
if f, found := fs.m[name]; found {
return &Fakefileinfo{f}, nil
// Exists returns true if file is known.
func (fs *FakeFS) Exists(name string) bool {
_, found := fs.m[name]
return found
}
// IsDir returns true if the file exists and is a directory.
func (fs *FakeFS) IsDir(name string) bool {
f, found := fs.m[name]
if !found {
return false
}
return nil, fmt.Errorf("file %q does not exist", name)
return f.dir
}
// ReadFile always returns an empty bytes and error depending on content of m.

View File

@@ -21,34 +21,25 @@ import (
"testing"
)
func TestStatNotExist(t *testing.T) {
func TestExists(t *testing.T) {
x := MakeFakeFS()
info, err := x.Stat("foo")
if info != nil {
t.Fatalf("expected nil info")
}
if err == nil {
t.Fatalf("expected error")
if x.Exists("foo") {
t.Fatalf("expected no foo")
}
}
func TestStat(t *testing.T) {
func TestIsDir(t *testing.T) {
x := MakeFakeFS()
expectedName := "my-dir"
err := x.Mkdir(expectedName, 0666)
err := x.Mkdir(expectedName)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
info, err := x.Stat(expectedName)
if err != nil {
t.Fatalf("unexpected error: %v", err)
if !x.Exists(expectedName) {
t.Fatalf(expectedName + " should exist")
}
name := info.Name()
if name != expectedName {
t.Fatalf("expected %v but got %v", expectedName, name)
}
if !info.IsDir() {
t.Fatalf("expected IsDir() return true")
if !x.IsDir(expectedName) {
t.Fatalf(expectedName + " should be a dir")
}
}
@@ -61,12 +52,8 @@ func TestCreate(t *testing.T) {
if err != nil {
t.Fatalf("unexpected error")
}
info, err := x.Stat("foo")
if info == nil {
t.Fatalf("expected non-nil info")
}
if err != nil {
t.Fatalf("expected no error")
if !x.Exists("foo") {
t.Fatalf("expected foo to exist")
}
}

View File

@@ -25,9 +25,10 @@ import (
// FileSystem groups basic os filesystem methods.
type FileSystem interface {
Create(name string) (File, error)
Mkdir(name string, perm os.FileMode) error
Mkdir(name string) error
Open(name string) (File, error)
Stat(name string) (os.FileInfo, error)
IsDir(name string) bool
Exists(name string) bool
ReadFile(name string) ([]byte, error)
ReadFiles(name string) (map[string][]byte, error)
WriteFile(name string, data []byte) error

View File

@@ -17,7 +17,6 @@ limitations under the License.
package fs
import (
"errors"
"os"
)
@@ -28,14 +27,6 @@ type realFile struct {
file *os.File
}
// MakeRealFile makes an instance of realFile.
func MakeRealFile(f *os.File) (File, error) {
if f == nil {
return nil, errors.New("file argument may not be nil")
}
return &realFile{file: f}, nil
}
// Close closes a file.
func (f *realFile) Close() error { return f.file.Close() }

View File

@@ -36,13 +36,27 @@ func MakeRealFS() FileSystem {
func (realFS) Create(name string) (File, error) { return os.Create(name) }
// Mkdir delegates to os.Mkdir.
func (realFS) Mkdir(name string, perm os.FileMode) error { return os.Mkdir(name, perm) }
func (realFS) Mkdir(name string) error {
return os.Mkdir(name, 0777|os.ModeDir)
}
// Open delegates to os.Open.
func (realFS) Open(name string) (File, error) { return os.Open(name) }
// Stat delegates to os.Stat.
func (realFS) Stat(name string) (os.FileInfo, error) { return os.Stat(name) }
// Exists returns true if os.Stat succeeds.
func (realFS) Exists(name string) bool {
_, err := os.Stat(name)
return err == nil
}
// IsDir delegates to os.Stat and FileInfo.IsDir
func (realFS) IsDir(name string) bool {
info, err := os.Stat(name)
if err != nil {
return false
}
return info.IsDir()
}
// ReadFile delegates to ioutil.ReadFile.
func (realFS) ReadFile(name string) ([]byte, error) { return ioutil.ReadFile(name) }

View File

@@ -26,17 +26,30 @@ import (
func TestReadFilesRealFS(t *testing.T) {
x := MakeRealFS()
testDir := "kustomize_testing_dir"
err := x.Mkdir(testDir, 0777)
err := x.Mkdir(testDir)
defer os.RemoveAll(testDir)
if err != nil {
t.Fatalf("unexpected error %s", err)
}
if !x.Exists(testDir) {
t.Fatalf("expected existence")
}
if !x.IsDir(testDir) {
t.Fatalf("expected directory")
}
err = x.WriteFile(path.Join(testDir, "foo"), []byte(`foo`))
if err != nil {
t.Fatalf("unexpected error %s", err)
}
if !x.Exists(path.Join(testDir, "foo")) {
t.Fatalf("expected foo")
}
if x.IsDir(path.Join(testDir, "foo")) {
t.Fatalf("expected foo not to be a directory")
}
err = x.WriteFile(path.Join(testDir, "bar"), []byte(`bar`))
if err != nil {
t.Fatalf("unexpected error %s", err)

View File

@@ -20,7 +20,7 @@ package error
import (
"fmt"
yaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apimachinery/pkg/util/yaml"
)
// YamlFormatError represents error with yaml file name where json/yaml format error happens.
@@ -33,8 +33,8 @@ func (e YamlFormatError) Error() string {
return fmt.Sprintf("YAML file [%s] encounters a format error.\n%s\n", e.Path, e.ErrorMsg)
}
// ErrorHandler handles YamlFormatError
func ErrorHandler(e error, path string) error {
// Handler handles YamlFormatError
func Handler(e error, path string) error {
if err, ok := e.(yaml.YAMLSyntaxError); ok {
return YamlFormatError{
Path: path,

View File

@@ -22,7 +22,7 @@ import (
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/constants"
yaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/apimachinery/pkg/util/yaml"
)
var (
@@ -49,9 +49,9 @@ func TestYamlFormatError_Error(t *testing.T) {
func TestErrorHandler(t *testing.T) {
f := foo{}
err := yaml.NewYAMLToJSONDecoder(bytes.NewReader([]byte(doc))).Decode(&f)
testErr := ErrorHandler(err, filepath)
expectedErr := fmt.Errorf("Format error message")
fmtErr := ErrorHandler(expectedErr, filepath)
testErr := Handler(err, filepath)
expectedErr := fmt.Errorf("format error message")
fmtErr := Handler(expectedErr, filepath)
if fmtErr.Error() != expectedErr.Error() {
t.Errorf("Expected returning fmt.Error, but found %T", fmtErr)
}

View File

@@ -18,8 +18,6 @@ limitations under the License.
package loadertest
import (
"os"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
)
@@ -36,11 +34,9 @@ type FakeLoader struct {
func NewFakeLoader(initialDir string) FakeLoader {
// Create fake filesystem and inject it into initial Loader.
fakefs := fs.MakeFakeFS()
var schemes []loader.SchemeLoader
schemes = append(schemes, loader.NewFileLoader(fakefs))
rootLoader := loader.Init(schemes)
loader, _ := rootLoader.New(initialDir)
return FakeLoader{fs: fakefs, delegate: loader}
rootLoader := loader.NewLoader(loader.NewFileLoader(fakefs))
ldr, _ := rootLoader.New(initialDir)
return FakeLoader{fs: fakefs, delegate: ldr}
}
// AddFile adds a fake file to the file system.
@@ -49,8 +45,8 @@ func (f FakeLoader) AddFile(fullFilePath string, content []byte) error {
}
// AddDirectory adds a fake directory to the file system.
func (f FakeLoader) AddDirectory(fullDirPath string, mode os.FileMode) error {
return f.fs.Mkdir(fullDirPath, mode)
func (f FakeLoader) AddDirectory(fullDirPath string) error {
return f.fs.Mkdir(fullDirPath)
}
// Root returns root.

View File

@@ -26,18 +26,19 @@ import (
const currentDir = "."
// Internal implementation of SchemeLoader interface.
type fileLoader struct {
// FileLoader loads files from a file system.
type FileLoader struct {
fs fs.FileSystem
}
// NewFileLoader returns a SchemeLoader to handle a file system.
func NewFileLoader(fs fs.FileSystem) SchemeLoader {
return &fileLoader{fs: fs}
// NewFileLoader returns a new FileLoader.
func NewFileLoader(fs fs.FileSystem) *FileLoader {
return &FileLoader{fs: fs}
}
// Is the location calculated with the root and location params a full file path.
func (l *fileLoader) IsScheme(root string, location string) bool {
// IsAbsPath return true if the location calculated with the root
// and location params a full file path.
func (l *FileLoader) IsAbsPath(root string, location string) bool {
fullFilePath, err := l.FullLocation(root, location)
if err != nil {
return false
@@ -45,14 +46,15 @@ func (l *fileLoader) IsScheme(root string, location string) bool {
return filepath.IsAbs(fullFilePath)
}
// FullLocation returns some notion of a full path.
// If location is a full file path, then ignore root. If location is relative, then
// join the root path with the location path. Either root or location can be empty,
// but not both. Special case for ".": Expands to current working directory.
// Example: "/home/seans/project", "subdir/bar" -> "/home/seans/project/subdir/bar".
func (l *fileLoader) FullLocation(root string, location string) (string, error) {
func (l *FileLoader) FullLocation(root string, location string) (string, error) {
// First, validate the parameters
if len(root) == 0 && len(location) == 0 {
return "", fmt.Errorf("Unable to calculate full location: root and location empty")
return "", fmt.Errorf("unable to calculate full location: root and location empty")
}
// Special case current directory, expanding to full file path.
if location == currentDir {
@@ -72,20 +74,12 @@ func (l *fileLoader) FullLocation(root string, location string) (string, error)
// Load returns the bytes from reading a file at fullFilePath.
// Implements the Loader interface.
func (l *fileLoader) Load(fullFilePath string) ([]byte, error) {
// Validate path to load from is a full file path.
if !filepath.IsAbs(fullFilePath) {
return nil, fmt.Errorf("Attempting to load file without full file path: %s\n", fullFilePath)
}
return l.fs.ReadFile(fullFilePath)
func (l *FileLoader) Load(p string) ([]byte, error) {
return l.fs.ReadFile(p)
}
// GlobLoad returns the map from path to bytes from reading a glob path.
// Implements the Loader interface.
func (l *fileLoader) GlobLoad(fullFilePath string) (map[string][]byte, error) {
// Validate path to load from is a full file path.
if !filepath.IsAbs(fullFilePath) {
return nil, fmt.Errorf("Attempting to load file without full file path: %s\n", fullFilePath)
}
return l.fs.ReadFiles(fullFilePath)
func (l *FileLoader) GlobLoad(p string) (map[string][]byte, error) {
return l.fs.ReadFiles(p)
}

View File

@@ -19,10 +19,9 @@ package loader
import "fmt"
// Loader interface exposes methods to read bytes in a scheme-agnostic manner.
// The Loader encapsulating a root location to calculate where to read from.
// Loader interface exposes methods to read bytes.
type Loader interface {
// Root returns the scheme-specific string representing the root location for this Loader.
// Root returns the root location for this Loader.
Root() string
// New returns Loader located at newRoot.
New(newRoot string) (Loader, error)
@@ -32,33 +31,20 @@ type Loader interface {
GlobLoad(location string) (map[string][]byte, error)
}
// Private implmentation of Loader interface.
// Private implementation of Loader interface.
type loaderImpl struct {
root string
schemes []SchemeLoader
}
// SchemeLoader is the interface for different types of loaders (e.g. fileLoader, httpLoader, etc.)
type SchemeLoader interface {
// Does this location correspond to this scheme.
IsScheme(root string, location string) bool
// Combines the root and path into a full location string.
FullLocation(root string, path string) (string, error)
// Load bytes at scheme-specific location or an error.
Load(location string) ([]byte, error)
// GlobLoad returns the bytes read from a glob path or an error.
GlobLoad(location string) (map[string][]byte, error)
fLoader *FileLoader
}
const emptyRoot = ""
// Init initializes the first loader with the supported schemes.
// Example schemes: fileLoader, httpLoader, gitLoader.
func Init(schemes []SchemeLoader) Loader {
return &loaderImpl{root: emptyRoot, schemes: schemes}
// NewLoader initializes the first loader with the supported fLoader.
func NewLoader(fl *FileLoader) Loader {
return &loaderImpl{root: emptyRoot, fLoader: fl}
}
// Root returns the scheme-specific root location for this Loader.
// Root returns the root location for this Loader.
func (l *loaderImpl) Root() string {
return l.root
}
@@ -69,53 +55,35 @@ func (l *loaderImpl) Root() string {
// Example: "/home/seans/project" or "/home/seans/project/"
// NOT "/home/seans/project/file.yaml".
func (l *loaderImpl) New(newRoot string) (Loader, error) {
scheme, err := l.getSchemeLoader(newRoot)
if !l.fLoader.IsAbsPath(l.root, newRoot) {
return nil, fmt.Errorf("Not abs path: l.root='%s', loc='%s'\n", l.root, newRoot)
}
root, err := l.fLoader.FullLocation(l.root, newRoot)
if err != nil {
return nil, err
}
root, err := scheme.FullLocation(l.root, newRoot)
if err != nil {
return nil, err
}
return &loaderImpl{root: root, schemes: l.schemes}, nil
return &loaderImpl{root: root, fLoader: l.fLoader}, nil
}
// Load returns all the bytes read from scheme-specific location or an error.
// Load returns all the bytes read from location or an error.
// "location" can be an absolute path, or if relative, full location is
// calculated from the Root().
func (l *loaderImpl) Load(location string) ([]byte, error) {
scheme, err := l.getSchemeLoader(location)
fullLocation, err := l.fLoader.FullLocation(l.root, location)
if err != nil {
fmt.Printf("Trouble in fulllocation: %v\n", err)
return nil, err
}
fullLocation, err := scheme.FullLocation(l.root, location)
if err != nil {
return nil, err
}
return scheme.Load(fullLocation)
return l.fLoader.Load(fullLocation)
}
// GlobLoad returns a map from path to bytes read from scheme-specific location or an error.
// GlobLoad returns a map from path to bytes read from the location or an error.
// "location" can be an absolute path, or if relative, full location is
// calculated from the Root().
func (l *loaderImpl) GlobLoad(location string) (map[string][]byte, error) {
scheme, err := l.getSchemeLoader(location)
fullLocation, err := l.fLoader.FullLocation(l.root, location)
if err != nil {
return nil, err
}
fullLocation, err := scheme.FullLocation(l.root, location)
if err != nil {
return nil, err
}
return scheme.GlobLoad(fullLocation)
}
// Helper function to parse scheme from location parameter.
func (l *loaderImpl) getSchemeLoader(location string) (SchemeLoader, error) {
for _, scheme := range l.schemes {
if scheme.IsScheme(l.root, location) {
return scheme, nil
}
}
return nil, fmt.Errorf("Unknown Scheme: %s, %s\n", l.root, location)
return l.fLoader.GlobLoad(fullLocation)
}

View File

@@ -25,10 +25,7 @@ import (
)
func initializeRootLoader(fakefs fs.FileSystem) Loader {
var schemes []SchemeLoader
schemes = append(schemes, NewFileLoader(fakefs))
rootLoader := Init(schemes)
return rootLoader
return NewLoader(NewFileLoader(fakefs))
}
func TestLoader_Root(t *testing.T) {
@@ -46,7 +43,7 @@ func TestLoader_Root(t *testing.T) {
}
_, err = rootLoader.New("https://google.com/project")
if err == nil {
t.Fatalf("Expected error for unknown scheme not returned")
t.Fatalf("Expected error")
}
// Test with trailing slash in directory.

View File

@@ -17,120 +17,27 @@ limitations under the License.
package resmap
import (
"fmt"
"strings"
cutil "github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret/util"
"github.com/kubernetes-sigs/kustomize/pkg/loader"
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/validation"
)
func newResourceFromConfigMap(l loader.Loader, cmArgs types.ConfigMapArgs) (*resource.Resource, error) {
cm, err := makeConfigMap(l, cmArgs)
if err != nil {
return nil, err
}
return resource.NewResourceWithBehavior(cm, resource.NewGenerationBehavior(cmArgs.Behavior))
}
func makeConfigMap(l loader.Loader, cmArgs types.ConfigMapArgs) (*corev1.ConfigMap, error) {
var envPairs, literalPairs, filePairs []kvPair
var err error
cm := &corev1.ConfigMap{}
cm.APIVersion = "v1"
cm.Kind = "ConfigMap"
cm.Name = cmArgs.Name
cm.Data = map[string]string{}
if cmArgs.EnvSource != "" {
envPairs, err = keyValuesFromEnvFile(l, cmArgs.EnvSource)
if err != nil {
return nil, fmt.Errorf("error reading keys from env source file: %s %v", cmArgs.EnvSource, err)
// NewResMapFromConfigMapArgs returns a Resource slice given
// a configmap metadata slice from kustomization file.
func NewResMapFromConfigMapArgs(
f *configmapandsecret.ConfigMapFactory,
cmArgsList []types.ConfigMapArgs) (ResMap, error) {
var allResources []*resource.Resource
for _, cmArgs := range cmArgsList {
if cmArgs.Behavior == "" {
cmArgs.Behavior = "create"
}
}
literalPairs, err = keyValuesFromLiteralSources(cmArgs.LiteralSources)
if err != nil {
return nil, fmt.Errorf("error reading key values from literal sources: %v", err)
}
filePairs, err = keyValuesFromFileSources(l, cmArgs.FileSources)
if err != nil {
return nil, fmt.Errorf("error reading key values from file sources: %v", err)
}
allPairs := append(append(envPairs, literalPairs...), filePairs...)
// merge key value pairs from all the sources
for _, kv := range allPairs {
err = addKV(cm.Data, kv)
if err != nil {
return nil, fmt.Errorf("error adding key in configmap: %v", err)
}
}
return cm, nil
}
func keyValuesFromEnvFile(l loader.Loader, path string) ([]kvPair, error) {
content, err := l.Load(path)
if err != nil {
return nil, err
}
return keyValuesFromLines(content)
}
func keyValuesFromLiteralSources(sources []string) ([]kvPair, error) {
var kvs []kvPair
for _, s := range sources {
// TODO: move ParseLiteralSource in this file
k, v, err := cutil.ParseLiteralSource(s)
cm, err := f.MakeConfigMap(&cmArgs)
if err != nil {
return nil, err
}
kvs = append(kvs, kvPair{key: k, value: v})
}
return kvs, nil
}
func keyValuesFromFileSources(l loader.Loader, sources []string) ([]kvPair, error) {
var kvs []kvPair
for _, s := range sources {
key, path, err := cutil.ParseFileSource(s)
if err != nil {
return nil, err
}
fileContent, err := l.Load(path)
if err != nil {
return nil, err
}
kvs = append(kvs, kvPair{key: key, value: string(fileContent)})
}
return kvs, nil
}
// addKV adds key-value pair to the provided map.
func addKV(m map[string]string, kv kvPair) error {
if errs := validation.IsConfigMapKey(kv.key); len(errs) != 0 {
return fmt.Errorf("%q is not a valid key name: %s", kv.key, strings.Join(errs, ";"))
}
if _, exists := m[kv.key]; exists {
return fmt.Errorf("key %s already exists: %v.", kv.key, m)
}
m[kv.key] = kv.value
return nil
}
// NewResMapFromConfigMapArgs returns a Resource slice given a configmap metadata slice from kustomization file.
func NewResMapFromConfigMapArgs(loader loader.Loader, cmList []types.ConfigMapArgs) (ResMap, error) {
allResources := []*resource.Resource{}
for _, cm := range cmList {
res, err := newResourceFromConfigMap(loader, cm)
res, err := resource.NewResourceWithBehavior(
cm, resource.NewGenerationBehavior(cmArgs.Behavior))
if err != nil {
return nil, err
}

View File

@@ -20,6 +20,8 @@ import (
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/internal/loadertest"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types"
@@ -38,6 +40,7 @@ func TestNewFromConfigMaps(t *testing.T) {
}
l := loadertest.NewFakeLoader("/home/seans/project/")
f := configmapandsecret.NewConfigMapFactory(fs.MakeFakeFS(), l)
testCases := []testCase{
{
description: "construct config map from env",
@@ -64,7 +67,7 @@ func TestNewFromConfigMaps(t *testing.T) {
"DB_USERNAME": "admin",
"DB_PASSWORD": "somepw",
},
}),
}).SetBehavior(resource.BehaviorCreate),
},
},
{
@@ -92,7 +95,7 @@ func TestNewFromConfigMaps(t *testing.T) {
BAR=baz
`,
},
}),
}).SetBehavior(resource.BehaviorCreate),
},
},
{
@@ -118,7 +121,7 @@ BAR=baz
"a": "x",
"b": "y",
},
}),
}).SetBehavior(resource.BehaviorCreate),
},
},
// TODO: add testcase for data coming from multiple sources like
@@ -126,11 +129,10 @@ BAR=baz
}
for _, tc := range testCases {
if ferr := l.AddFile(tc.filepath, []byte(tc.content)); ferr != nil {
t.Fatalf("Error adding fake file: %v\n", ferr)
}
r, err := NewResMapFromConfigMapArgs(l, tc.input)
r, err := NewResMapFromConfigMapArgs(f, tc.input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

View File

@@ -39,7 +39,7 @@ type ResMap map[resource.ResId]*resource.Resource
// EncodeAsYaml encodes a ResMap to YAML; encoded objects separated by `---`.
func (m ResMap) EncodeAsYaml() ([]byte, error) {
ids := []resource.ResId{}
var ids []resource.ResId
for id := range m {
ids = append(ids, id)
}
@@ -73,8 +73,8 @@ func (m ResMap) EncodeAsYaml() ([]byte, error) {
// ErrorIfNotEqual returns error if maps are not equal.
func (m ResMap) ErrorIfNotEqual(m2 ResMap) error {
if len(m) != len(m2) {
keySet1 := []resource.ResId{}
keySet2 := []resource.ResId{}
var keySet1 []resource.ResId
var keySet2 []resource.ResId
for id := range m {
keySet1 = append(keySet1, id)
}
@@ -101,7 +101,7 @@ func (m ResMap) insert(newName string, obj *unstructured.Unstructured) error {
id := resource.NewResId(gvk, oldName)
if _, found := m[id]; found {
return fmt.Errorf("The <name: %q, GroupVersionKind: %v> already exists in the map", oldName, gvk)
return fmt.Errorf("the <name: %q, GroupVersionKind: %v> already exists in the map", oldName, gvk)
}
obj.SetName(newName)
m[id] = resource.NewResourceFromUnstruct(*obj)
@@ -111,7 +111,7 @@ func (m ResMap) insert(newName string, obj *unstructured.Unstructured) error {
// NewResourceSliceFromPatches returns a slice of resources given a patch path slice from a kustomization file.
func NewResourceSliceFromPatches(
loader loader.Loader, paths []string) ([]*resource.Resource, error) {
result := []*resource.Resource{}
var result []*resource.Resource
for _, path := range paths {
contents, err := loader.GlobLoad(path)
if err != nil {
@@ -120,7 +120,7 @@ func NewResourceSliceFromPatches(
for p, content := range contents {
res, err := newResourceSliceFromBytes(content)
if err != nil {
return nil, internal.ErrorHandler(err, p)
return nil, internal.Handler(err, p)
}
result = append(result, res...)
@@ -131,7 +131,7 @@ func NewResourceSliceFromPatches(
// NewResMapFromFiles returns a ResMap given a resource path slice.
func NewResMapFromFiles(loader loader.Loader, paths []string) (ResMap, error) {
result := []ResMap{}
var result []ResMap
for _, path := range paths {
contents, err := loader.GlobLoad(path)
if err != nil {
@@ -140,7 +140,7 @@ func NewResMapFromFiles(loader loader.Loader, paths []string) (ResMap, error) {
for p, content := range contents {
res, err := newResMapFromBytes(content)
if err != nil {
return nil, internal.ErrorHandler(err, p)
return nil, internal.Handler(err, p)
}
result = append(result, res)
}
@@ -180,8 +180,7 @@ func newResMapFromResourceSlice(resources []*resource.Resource) (ResMap, error)
func newResourceSliceFromBytes(in []byte) ([]*resource.Resource, error) {
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024)
result := []*resource.Resource{}
var result []*resource.Resource
var err error
for {
var out unstructured.Unstructured
@@ -201,11 +200,11 @@ func newResourceSliceFromBytes(in []byte) ([]*resource.Resource, error) {
func MergeWithoutOverride(maps ...ResMap) (ResMap, error) {
result := ResMap{}
for _, m := range maps {
for id, resource := range m {
for id, res := range m {
if _, found := result[id]; found {
return nil, fmt.Errorf("id '%q' already used", id)
}
result[id] = resource
result[id] = res
}
}
return result, nil
@@ -235,12 +234,12 @@ func MergeWithOverride(maps ...ResMap) (ResMap, error) {
glog.V(4).Infof("Merged object is %v", result[id].Object)
result[id].SetBehavior(resource.BehaviorCreate)
default:
return nil, fmt.Errorf("Id %#v exists; must merge or replace.", id)
return nil, fmt.Errorf("id %#v exists; must merge or replace", id)
}
} else {
switch r.Behavior() {
case resource.BehaviorMerge, resource.BehaviorReplace:
return nil, fmt.Errorf("Id %#v does not exist; cannot merge or replace.", id)
return nil, fmt.Errorf("id %#v does not exist; cannot merge or replace", id)
default:
result[id] = r
}

View File

@@ -17,68 +17,30 @@ limitations under the License.
package resmap
import (
"context"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
)
func newResourceFromSecretGenerator(p string, sArgs types.SecretArgs) (*resource.Resource, error) {
s, err := makeSecret(p, sArgs)
if err != nil {
return nil, errors.Wrap(err, "makeSecret")
}
return resource.NewResourceWithBehavior(
s, resource.NewGenerationBehavior(sArgs.Behavior))
}
func makeSecret(p string, sArgs types.SecretArgs) (*corev1.Secret, error) {
s := &corev1.Secret{}
s.APIVersion = "v1"
s.Kind = "Secret"
s.Name = sArgs.Name
s.Type = corev1.SecretType(sArgs.Type)
if s.Type == "" {
s.Type = corev1.SecretTypeOpaque
}
s.Data = map[string][]byte{}
for k, v := range sArgs.Commands {
out, err := createSecretKey(p, v)
// NewResMapFromSecretArgs takes a SecretArgs slice, generates
// secrets from each entry, and accumulates them in a ResMap.
func NewResMapFromSecretArgs(
f *configmapandsecret.SecretFactory,
secretList []types.SecretArgs) (ResMap, error) {
var allResources []*resource.Resource
for _, args := range secretList {
s, err := f.MakeSecret(args)
if err != nil {
return nil, errors.Wrap(err, "createSecretKey")
return nil, errors.Wrap(err, "makeSecret")
}
s.Data[k] = out
}
return s, nil
}
func createSecretKey(wd string, command string) ([]byte, error) {
fi, err := os.Stat(wd)
if err != nil || !fi.IsDir() {
wd = filepath.Dir(wd)
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "sh", "-c", command)
cmd.Dir = wd
return cmd.Output()
}
// NewResMapFromSecretArgs takes a SecretArgs slice and executes its command in directory p
// then writes the output to a Resource slice and return it.
func NewResMapFromSecretArgs(p string, secretList []types.SecretArgs) (ResMap, error) {
allResources := []*resource.Resource{}
for _, secret := range secretList {
res, err := newResourceFromSecretGenerator(p, secret)
if args.Behavior == "" {
args.Behavior = "create"
}
res, err := resource.NewResourceWithBehavior(
s, resource.NewGenerationBehavior(args.Behavior))
if err != nil {
return nil, errors.Wrap(err, "newResourceFromSecretGenerator")
return nil, errors.Wrap(err, "NewResourceWithBehavior")
}
allResources = append(allResources, res)
}

View File

@@ -21,6 +21,8 @@ import (
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/configmapandsecret"
"github.com/kubernetes-sigs/kustomize/pkg/fs"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types"
corev1 "k8s.io/api/core/v1"
@@ -40,7 +42,10 @@ func TestNewResMapFromSecretArgs(t *testing.T) {
Type: "Opaque",
},
}
actual, err := NewResMapFromSecretArgs(".", secrets)
fakeFs := fs.MakeFakeFS()
fakeFs.Mkdir(".")
actual, err := NewResMapFromSecretArgs(
configmapandsecret.NewSecretFactory(fakeFs, "."), secrets)
if err != nil {
t.Fatalf("unexpected error: %v", err)
@@ -60,7 +65,7 @@ func TestNewResMapFromSecretArgs(t *testing.T) {
"DB_USERNAME": base64.StdEncoding.EncodeToString([]byte("admin")),
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
},
}),
}).SetBehavior(resource.BehaviorCreate),
}
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("%#v\ndoesn't match expected:\n%#v", actual, expected)

View File

@@ -62,8 +62,14 @@ func (r *Resource) Behavior() GenerationBehavior {
}
// SetBehavior changes the resource to the new behavior
func (r *Resource) SetBehavior(b GenerationBehavior) {
func (r *Resource) SetBehavior(b GenerationBehavior) *Resource {
r.b = b
return r
}
// IsGenerated checks if the resource is generated from a generator
func (r *Resource) IsGenerated() bool {
return r.b != BehaviorUnspecified
}
// Id returns the ResId for the resource.
@@ -115,7 +121,7 @@ func (r *Resource) GetFieldValue(fieldPath string) (string, error) {
func getFieldValue(m map[string]interface{}, pathToField []string) (string, error) {
if len(pathToField) == 0 {
return "", fmt.Errorf("Field not found")
return "", fmt.Errorf("field not found")
}
if len(pathToField) == 1 {
if v, found := m[pathToField[0]]; found {

View File

@@ -0,0 +1,120 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package transformers
import (
"strings"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/types"
)
// imageTagTransformer replace image tags
type imageTagTransformer struct {
imageTags []types.ImageTag
}
var _ Transformer = &imageTagTransformer{}
// NewImageTagTransformer constructs a imageTagTransformer.
func NewImageTagTransformer(slice []types.ImageTag) (Transformer, error) {
return &imageTagTransformer{slice}, nil
}
// Transform finds the matching images and replace the tag
func (pt *imageTagTransformer) Transform(resources resmap.ResMap) error {
if len(pt.imageTags) == 0 {
return nil
}
for _, res := range resources {
err := pt.findAndReplaceTag(res.UnstructuredContent())
if err != nil {
return err
}
}
return nil
}
/*
findAndReplaceTag replaces the image tags inside one object
It searches the object for container session
then loops though all images inside containers session, finds matched ones and update the tag name
*/
func (pt *imageTagTransformer) findAndReplaceTag(obj map[string]interface{}) error {
paths := []string{"containers", "initContainers"}
found := false
for _, path := range paths {
_, found = obj[path]
if found {
err := pt.updateContainers(obj, path)
if err != nil {
return err
}
}
}
if !found {
return pt.findContainers(obj)
}
return nil
}
func (pt *imageTagTransformer) updateContainers(obj map[string]interface{}, path string) error {
containers := obj[path].([]interface{})
for i := range containers {
container := containers[i].(map[string]interface{})
image, found := container["image"]
if !found {
continue
}
for _, imagetag := range pt.imageTags {
if isImageMatched(image.(string), imagetag.Name) {
container["image"] = strings.Join([]string{imagetag.Name, imagetag.NewTag}, ":")
break
}
}
}
return nil
}
func (pt *imageTagTransformer) findContainers(obj map[string]interface{}) error {
for key := range obj {
switch typedV := obj[key].(type) {
case map[string]interface{}:
err := pt.findAndReplaceTag(typedV)
if err != nil {
return err
}
case []interface{}:
for i := range typedV {
item := typedV[i]
typedItem, ok := item.(map[string]interface{})
if ok {
err := pt.findAndReplaceTag(typedItem)
if err != nil {
return err
}
}
}
}
}
return nil
}
func isImageMatched(s, t string) bool {
imagetag := strings.Split(s, ":")
return len(imagetag) >= 1 && imagetag[0] == t
}

View File

@@ -0,0 +1,174 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package transformers
import (
"reflect"
"testing"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"github.com/kubernetes-sigs/kustomize/pkg/types"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestImageTagTransformer(t *testing.T) {
m := resmap.ResMap{
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
map[string]interface{}{
"group": "apps",
"apiVersion": "v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"initContainers": []interface{}{
map[string]interface{}{
"name": "nginx2",
"image": "my-nginx:1.8.0",
},
},
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:1.7.9",
},
},
},
},
},
}),
resource.NewResId(schema.GroupVersionKind{Kind: "randomeKind"}, "random"): resource.NewResourceFromMap(
map[string]interface{}{
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx1",
"image": "nginx",
},
map[string]interface{}{
"name": "nginx2",
"image": "my-nginx:random",
},
},
},
},
},
"spec2": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "ngin3",
"image": "nginx:v1",
},
map[string]interface{}{
"name": "nginx4",
"image": "my-nginx:latest",
},
},
},
},
},
}),
}
expected := resmap.ResMap{
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
map[string]interface{}{
"group": "apps",
"apiVersion": "v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "deploy1",
},
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"initContainers": []interface{}{
map[string]interface{}{
"name": "nginx2",
"image": "my-nginx:previous",
},
},
"containers": []interface{}{
map[string]interface{}{
"name": "nginx",
"image": "nginx:v2",
},
},
},
},
},
}),
resource.NewResId(schema.GroupVersionKind{Kind: "randomeKind"}, "random"): resource.NewResourceFromMap(
map[string]interface{}{
"spec": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "nginx1",
"image": "nginx:v2",
},
map[string]interface{}{
"name": "nginx2",
"image": "my-nginx:previous",
},
},
},
},
},
"spec2": map[string]interface{}{
"template": map[string]interface{}{
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "ngin3",
"image": "nginx:v2",
},
map[string]interface{}{
"name": "nginx4",
"image": "my-nginx:previous",
},
},
},
},
},
}),
}
it, err := NewImageTagTransformer([]types.ImageTag{
{Name: "nginx", NewTag: "v2"},
{Name: "my-nginx", NewTag: "previous"},
})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
err = it.Transform(m)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(m, expected) {
err = expected.ErrorIfNotEqual(m)
t.Fatalf("actual doesn't match expected: %v", err)
}
}

View File

@@ -162,7 +162,7 @@ var defaultLabelsPathConfigs = []PathConfig{
{
GroupVersionKind: &schema.GroupVersionKind{Group: "networking.k8s.io", Kind: "NetworkPolicy"},
Path: []string{"spec", "podSelector", "matchLabels"},
CreateIfNotPresent: true,
CreateIfNotPresent: false,
},
{
GroupVersionKind: &schema.GroupVersionKind{Group: "networking.k8s.io", Kind: "NetworkPolicy"},

View File

@@ -41,17 +41,19 @@ func NewNameHashTransformer() Transformer {
// Transform appends hash to configmaps and secrets.
func (o *nameHashTransformer) Transform(m resmap.ResMap) error {
for id, res := range m {
switch {
case selectByGVK(id.Gvk(), &schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}):
err := appendHashForConfigMap(res)
if err != nil {
return err
}
if res.IsGenerated() {
switch {
case selectByGVK(id.Gvk(), &schema.GroupVersionKind{Version: "v1", Kind: "ConfigMap"}):
err := appendHashForConfigMap(res)
if err != nil {
return err
}
case selectByGVK(id.Gvk(), &schema.GroupVersionKind{Version: "v1", Kind: "Secret"}):
err := appendHashForSecret(res)
if err != nil {
return err
case selectByGVK(id.Gvk(), &schema.GroupVersionKind{Version: "v1", Kind: "Secret"}):
err := appendHashForSecret(res)
if err != nil {
return err
}
}
}
}

View File

@@ -83,7 +83,7 @@ func TestNameHashTransformer(t *testing.T) {
"metadata": map[string]interface{}{
"name": "secret1",
},
}),
}).SetBehavior(resource.BehaviorCreate),
}
expected := resmap.ResMap{
@@ -92,7 +92,7 @@ func TestNameHashTransformer(t *testing.T) {
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": map[string]interface{}{
"name": "cm1-m462kdfb68",
"name": "cm1",
},
}),
resource.NewResId(deploy, "deploy1"): resource.NewResourceFromMap(
@@ -144,7 +144,7 @@ func TestNameHashTransformer(t *testing.T) {
"metadata": map[string]interface{}{
"name": "secret1-7kc45hd5f7",
},
}),
}).SetBehavior(resource.BehaviorCreate),
}
tran := NewNameHashTransformer()

View File

@@ -871,8 +871,7 @@ func AddNameReferencePathConfigs(r []ReferencePathConfig) {
// MergeNameReferencePathConfigs merges one ReferencePathConfig into a slice of ReferencePathConfig
func MergeNameReferencePathConfigs(configs []ReferencePathConfig, config ReferencePathConfig) []ReferencePathConfig {
result := []ReferencePathConfig{}
var result []ReferencePathConfig
found := false
for _, c := range configs {
if c.referencedGVK == config.referencedGVK {

View File

@@ -20,8 +20,7 @@ import (
"encoding/json"
"fmt"
jsonpatch "github.com/evanphx/json-patch"
"github.com/evanphx/json-patch"
"github.com/kubernetes-sigs/kustomize/pkg/resmap"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"k8s.io/apimachinery/pkg/runtime"

View File

@@ -19,7 +19,7 @@ package transformers
import (
"encoding/json"
jsonpatch "github.com/evanphx/json-patch"
"github.com/evanphx/json-patch"
"github.com/kubernetes-sigs/kustomize/pkg/resource"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/mergepatch"

View File

@@ -68,6 +68,12 @@ type Kustomization struct {
// Variables which will be substituted at runtime
Vars []Var `json:"vars,omitempty" yaml:"vars,omitempty"`
// If set to true, all images need to have tags
RequireTag bool `json:"requireTag,omitempty" yaml:"requireTag,omitempty"`
// ImageTags is a list of ImageTag for changing image tags
ImageTags []ImageTag `json:"imageTags,omitempty" yaml:"imageTags,omitempty"`
}
// ConfigMapArgs contains the metadata of how to generate a configmap.
@@ -136,3 +142,12 @@ type DataSources struct {
// i.e. a Docker .env file or a .ini file.
EnvSource string `json:"env,omitempty" yaml:"env,omitempty"`
}
// ImageTag contains an image and a new tag, which will replace the original tag.
type ImageTag struct {
// Name is a tag-less image name.
Name string `json:"name,omitempty" yaml:"name,omitempty"`
// NewTag is the value to use in replacing the original tag.
NewTag string `json:"newTag,omitempty" yaml:"newTag,omitempty"`
}