feat: Advanced Templating (#823)
1. Added `helmfile build` command to print final state Motivation: useful for debugging purposes and some CI scenarios Ref #780 2. Template interpolation is now recursive (you can cross-reference release fields) like: ```yaml templates: release: name: {{`app-{{ .Release.Namespace }}`}} namespace: {{`{{ .Release.Labels.ns }}`}} labels: ns: dev ``` 3. Experimental: Added some boolean release fields interpolation in templates: ```yaml templates: release: name: {{`app-{{ .Release.Namespace }}`}} namespace: dev installedTemplate: {{`{{ eq .Release.Namespace "dev" }}`}} ``` Resolves #818 4. Added more template interpolations: Labels, SetValues 5. Added template interpolation for inline Values 6. Added `helmfile list` command to print target releases in simple tabular form 7. Added release names in some `helm` output messages, e.g.: `Comparing release=%v, chart=%v`
This commit is contained in:
		
							parent
							
								
									dd58badf81
								
							
						
					
					
						commit
						11d0abba6e
					
				|  | @ -1,3 +1,5 @@ | |||
| dist/ | ||||
| .idea/ | ||||
| helmfile | ||||
| helmfile | ||||
| helmfile.lock | ||||
| vendor/ | ||||
|  |  | |||
							
								
								
									
										2
									
								
								Makefile
								
								
								
								
							
							
						
						
									
										2
									
								
								Makefile
								
								
								
								
							|  | @ -31,7 +31,7 @@ cross: | |||
| 
 | ||||
| static-linux: | ||||
| 	env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOFLAGS=-mod=vendor go build -o "dist/helmfile_linux_amd64" -ldflags '-X main.Version=${TAG}' ${TARGETS} | ||||
| .PHONY: linux | ||||
| .PHONY: static-linux | ||||
| 
 | ||||
| install: | ||||
| 	env CGO_ENABLED=0 go install -ldflags '-X main.Version=${TAG}' ${TARGETS} | ||||
|  |  | |||
|  | @ -89,6 +89,42 @@ releases: | |||
|   <<: *default | ||||
| ``` | ||||
| 
 | ||||
| Release Templating supports the following parts of release definition: | ||||
| - basic fields: `name`, `namespace`, `chart`, `version` | ||||
| - boolean fields: `installed`, `wait`, `tillerless`, `verify` by the means of additional text | ||||
|   fields designed for templating only: `installedTemplate`, `waitTemplate`, `tillerlessTemplate`, `verifyTemplate` | ||||
|   ```yaml | ||||
|   # ... | ||||
|     installedTemplate: '{{`{{ eq .Release.Namespace "kube-system" }}`}}' | ||||
|     waitTemplate: '{{`{{ eq .Release.Labels.tag "safe" | not }}`}}' | ||||
|   # ... | ||||
|   ``` | ||||
| - `set` block values: | ||||
|   ```yaml | ||||
|   # ... | ||||
|     set: | ||||
|     - name: '{{`{{ .Release.Name }}`}}' | ||||
|       values: '{{`{{ .Release.Namespace }}`}}' | ||||
|   # ... | ||||
|   ``` | ||||
| - `values` and `secrets` file paths: | ||||
|   ```yaml | ||||
|   # ... | ||||
|     values: | ||||
|     - config/{{`{{ .Release.Name }}`}}/values.yaml | ||||
|     secrets: | ||||
|     - config/{{`{{ .Release.Name }}`}}/secrets.yaml | ||||
|   # ... | ||||
|   ``` | ||||
| - inline `values` map: | ||||
|   ```yaml | ||||
|   # ... | ||||
|     values: | ||||
|     - image: | ||||
|         tag: `{{ .Release.Labels.tag }}` | ||||
|   # ... | ||||
|   ``` | ||||
| 
 | ||||
| See the [issue 428](https://github.com/roboll/helmfile/issues/428) for more context on how this is supposed to work. | ||||
| 
 | ||||
| ## Layering State Files | ||||
|  |  | |||
							
								
								
									
										3
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										3
									
								
								go.mod
								
								
								
								
							|  | @ -6,9 +6,10 @@ require ( | |||
| 	github.com/Masterminds/goutils v1.1.0 // indirect | ||||
| 	github.com/Masterminds/semver v1.4.1 | ||||
| 	github.com/Masterminds/sprig v2.20.0+incompatible | ||||
| 	github.com/aokoli/goutils v1.0.1 // indirect | ||||
| 	github.com/go-test/deep v1.0.3 | ||||
| 	github.com/google/go-cmp v0.3.0 | ||||
| 	github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c // indirect | ||||
| 	github.com/gosuri/uitable v0.0.3 | ||||
| 	github.com/hashicorp/go-getter v1.3.0 | ||||
| 	github.com/huandu/xstrings v1.2.0 // indirect | ||||
| 	github.com/imdario/mergo v0.3.6 | ||||
|  |  | |||
							
								
								
									
										24
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										24
									
								
								go.sum
								
								
								
								
							|  | @ -12,15 +12,9 @@ github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RP | |||
| github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= | ||||
| github.com/Masterminds/semver v1.4.1 h1:CaDA1wAoM3rj9sAFyyZP37LloExUzxFGYt+DqJ870JA= | ||||
| github.com/Masterminds/semver v1.4.1/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= | ||||
| github.com/Masterminds/sprig v2.15.0+incompatible h1:0gSxPGWS9PAr7U2NsQ2YQg6juRDINkUyuvbb4b2Xm8w= | ||||
| github.com/Masterminds/sprig v2.15.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= | ||||
| github.com/Masterminds/sprig v2.16.0+incompatible h1:QZbMUPxRQ50EKAq3LFMnxddMu88/EUUG3qmxwtDmPsY= | ||||
| github.com/Masterminds/sprig v2.16.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= | ||||
| github.com/Masterminds/sprig v2.20.0+incompatible h1:dJTKKuUkYW3RMFdQFXPU/s6hg10RgctmTjRcbZ98Ap8= | ||||
| github.com/Masterminds/sprig v2.20.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= | ||||
| github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= | ||||
| github.com/aokoli/goutils v1.0.1 h1:7fpzNGoJ3VA8qcrm++XEE1QUe0mIwNeLa02Nwq7RDkg= | ||||
| github.com/aokoli/goutils v1.0.1/go.mod h1:SijmP0QR8LtwsmDs8Yii5Z/S4trXFGFC2oO5g9DP+DQ= | ||||
| github.com/aws/aws-sdk-go v1.15.78 h1:LaXy6lWR0YK7LKyuU0QWy2ws/LWTPfYV/UgfiBu4tvY= | ||||
| github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= | ||||
| github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= | ||||
|  | @ -30,14 +24,19 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT | |||
| github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= | ||||
| github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | ||||
| github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= | ||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= | ||||
| github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= | ||||
| github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | ||||
| github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= | ||||
| github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | ||||
| github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= | ||||
| github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= | ||||
| github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= | ||||
| github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= | ||||
| github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= | ||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= | ||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||||
| github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= | ||||
| github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= | ||||
|  | @ -50,6 +49,7 @@ github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= | |||
| github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= | ||||
| github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= | ||||
| github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= | ||||
| github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= | ||||
| github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= | ||||
| github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= | ||||
| github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c h1:jWtZjFEUE/Bz0IeIhqCnyZ3HG6KRXSntXe4SjtuTH7c= | ||||
|  | @ -59,6 +59,8 @@ github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk | |||
| github.com/googleapis/gax-go/v2 v2.0.3 h1:siORttZ36U2R/WjiJuDz8znElWBiAlO9rVt+mqJt0Cc= | ||||
| github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= | ||||
| github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | ||||
| github.com/gosuri/uitable v0.0.3 h1:9ZY4qCODg6JL1Ui4dL9LqCF4ghWnAOSV2h7xG98SkHE= | ||||
| github.com/gosuri/uitable v0.0.3/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= | ||||
| github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= | ||||
| github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= | ||||
| github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= | ||||
|  | @ -69,8 +71,6 @@ github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhE | |||
| github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= | ||||
| github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= | ||||
| github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= | ||||
| github.com/huandu/xstrings v1.0.0 h1:pO2K/gKgKaat5LdpAhxhluX2GPQMaI3W5FUz/I/UnWk= | ||||
| github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= | ||||
| github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= | ||||
| github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= | ||||
| github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= | ||||
|  | @ -83,7 +83,9 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN | |||
| github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||
| github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||
| github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= | ||||
| github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= | ||||
| github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= | ||||
| github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= | ||||
| github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= | ||||
| github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= | ||||
|  | @ -98,6 +100,7 @@ github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a | |||
| github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= | ||||
| github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= | ||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= | ||||
| github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | ||||
|  | @ -129,6 +132,7 @@ github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYED | |||
| github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= | ||||
| github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= | ||||
| github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= | ||||
| github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= | ||||
| github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||||
| github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= | ||||
| github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939 h1:BhIUXV2ySTLrKgh/Hnts+QTQlIbWtomXt3LMdzME0A0= | ||||
|  | @ -147,8 +151,6 @@ go.uber.org/zap v1.8.0 h1:r6Za1Rii8+EGOYRDLvpooNOF6kP3iyDnkpzbw67gCQ8= | |||
| go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= | ||||
| go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= | ||||
| golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= | ||||
| golang.org/x/crypto v0.0.0-20180403160946-b2aa35443fbc h1:Kx1Ke+iCR1aDjbWXgmEQGFxoHtNL49aRZGV7/+jJ41Y= | ||||
| golang.org/x/crypto v0.0.0-20180403160946-b2aa35443fbc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA= | ||||
| golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
|  | @ -167,6 +169,7 @@ golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890 h1:uESlIz09WIHT2I+pasSXcp | |||
| golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= | ||||
| golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ= | ||||
| golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
|  | @ -198,6 +201,7 @@ google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmE | |||
| google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= | ||||
| google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk= | ||||
| google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||
| gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= | ||||
| gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= | ||||
|  |  | |||
							
								
								
									
										21
									
								
								main.go
								
								
								
								
							
							
						
						
									
										21
									
								
								main.go
								
								
								
								
							|  | @ -2,6 +2,9 @@ package main | |||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/roboll/helmfile/pkg/app" | ||||
| 	"github.com/roboll/helmfile/pkg/helmexec" | ||||
| 	"github.com/roboll/helmfile/pkg/maputil" | ||||
|  | @ -9,8 +12,6 @@ import ( | |||
| 	"github.com/urfave/cli" | ||||
| 	"go.uber.org/zap" | ||||
| 	"go.uber.org/zap/zapcore" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| var Version string | ||||
|  | @ -392,6 +393,22 @@ func main() { | |||
| 				return run.Test(c) | ||||
| 			}), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:  "build", | ||||
| 			Usage: "output compiled helmfile state(s) as YAML", | ||||
| 			Flags: []cli.Flag{}, | ||||
| 			Action: action(func(run *app.App, c configImpl) error { | ||||
| 				return run.PrintState(c) | ||||
| 			}), | ||||
| 		}, | ||||
| 		{ | ||||
| 			Name:  "list", | ||||
| 			Usage: "list releases defined in state file", | ||||
| 			Flags: []cli.Flag{}, | ||||
| 			Action: action(func(run *app.App, c configImpl) error { | ||||
| 				return run.ListReleases(c) | ||||
| 			}), | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	err := cliApp.Run(os.Args) | ||||
|  |  | |||
|  | @ -9,6 +9,7 @@ import ( | |||
| 	"strings" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"github.com/gosuri/uitable" | ||||
| 	"github.com/roboll/helmfile/pkg/helmexec" | ||||
| 	"github.com/roboll/helmfile/pkg/remote" | ||||
| 	"github.com/roboll/helmfile/pkg/state" | ||||
|  | @ -158,6 +159,38 @@ func (a *App) Test(c TestConfigProvider) error { | |||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func (a *App) PrintState(c StateConfigProvider) error { | ||||
| 
 | ||||
| 	return a.ForEachState(func(run *Run) []error { | ||||
| 		state, err := run.state.ToYaml() | ||||
| 		if err != nil { | ||||
| 			return []error{err} | ||||
| 		} | ||||
| 		fmt.Printf("---\n#  Source: %s\n\n%+v", run.state.FilePath, state) | ||||
| 		return []error{} | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func (a *App) ListReleases(c StateConfigProvider) error { | ||||
| 	table := uitable.New() | ||||
| 	table.AddRow("NAME", "NAMESPACE", "INSTALLED", "LABELS") | ||||
| 
 | ||||
| 	err := a.ForEachState(func(run *Run) []error { | ||||
| 		//var releases m
 | ||||
| 		for _, r := range run.state.Releases { | ||||
| 			labels := "" | ||||
| 			for k, v := range r.Labels { | ||||
| 				labels = fmt.Sprintf("%s,%s:%s", labels, k, v) | ||||
| 			} | ||||
| 			installed := r.Installed == nil || *r.Installed | ||||
| 			table.AddRow(r.Name, r.Namespace, fmt.Sprintf("%t", installed), strings.Trim(labels, ",")) | ||||
| 		} | ||||
| 		return []error{} | ||||
| 	}) | ||||
| 	fmt.Println(table.String()) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (a *App) within(dir string, do func() error) error { | ||||
| 	if dir == "." { | ||||
| 		return do() | ||||
|  |  | |||
|  | @ -3,15 +3,21 @@ package app | |||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"github.com/roboll/helmfile/pkg/helmexec" | ||||
| 	"github.com/roboll/helmfile/pkg/state" | ||||
| 	"github.com/roboll/helmfile/pkg/testhelper" | ||||
| 	"gotest.tools/assert" | ||||
| 	"io" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"reflect" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/roboll/helmfile/pkg/helmexec" | ||||
| 	"github.com/roboll/helmfile/pkg/state" | ||||
| 	"github.com/roboll/helmfile/pkg/testhelper" | ||||
| 
 | ||||
| 	"go.uber.org/zap" | ||||
| 	"gotest.tools/env" | ||||
| ) | ||||
|  | @ -1840,7 +1846,7 @@ type mockTemplates struct { | |||
| 	flags []string | ||||
| } | ||||
| 
 | ||||
| func (helm *mockHelmExec) TemplateRelease(chart string, flags ...string) error { | ||||
| func (helm *mockHelmExec) TemplateRelease(name, chart string, flags ...string) error { | ||||
| 	helm.templated = append(helm.templated, mockTemplates{flags: flags}) | ||||
| 	return nil | ||||
| } | ||||
|  | @ -1849,7 +1855,7 @@ func (helm *mockHelmExec) UpdateDeps(chart string) error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (helm *mockHelmExec) BuildDeps(chart string) error { | ||||
| func (helm *mockHelmExec) BuildDeps(name, chart string) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | @ -1889,7 +1895,7 @@ func (helm *mockHelmExec) TestRelease(context helmexec.HelmContext, name string, | |||
| func (helm *mockHelmExec) Fetch(chart string, flags ...string) error { | ||||
| 	return nil | ||||
| } | ||||
| func (helm *mockHelmExec) Lint(chart string, flags ...string) error { | ||||
| func (helm *mockHelmExec) Lint(name, chart string, flags ...string) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
|  | @ -1938,3 +1944,160 @@ releases: | |||
| 
 | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func captureStdout(f func()) string { | ||||
| 	reader, writer, err := os.Pipe() | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	stdout := os.Stdout | ||||
| 	defer func() { | ||||
| 		os.Stdout = stdout | ||||
| 		log.SetOutput(os.Stderr) | ||||
| 	}() | ||||
| 	os.Stdout = writer | ||||
| 	log.SetOutput(writer) | ||||
| 	out := make(chan string) | ||||
| 	wg := new(sync.WaitGroup) | ||||
| 	wg.Add(1) | ||||
| 	go func() { | ||||
| 		var buf bytes.Buffer | ||||
| 		wg.Done() | ||||
| 		io.Copy(&buf, reader) | ||||
| 		out <- buf.String() | ||||
| 	}() | ||||
| 	wg.Wait() | ||||
| 	f() | ||||
| 	writer.Close() | ||||
| 	return <-out | ||||
| } | ||||
| 
 | ||||
| func TestPrint_SingleStateFile(t *testing.T) { | ||||
| 	files := map[string]string{ | ||||
| 		"/path/to/helmfile.yaml": ` | ||||
| releases: | ||||
| - name: myrelease1 | ||||
|   chart: mychart1 | ||||
| - name: myrelease2 | ||||
|   chart: mychart1 | ||||
| `, | ||||
| 	} | ||||
| 	stdout := os.Stdout | ||||
| 	defer func() { os.Stdout = stdout }() | ||||
| 
 | ||||
| 	var buffer bytes.Buffer | ||||
| 	logger := helmexec.NewLogger(&buffer, "debug") | ||||
| 
 | ||||
| 	app := appWithFs(&App{ | ||||
| 		glob:        filepath.Glob, | ||||
| 		abs:         filepath.Abs, | ||||
| 		KubeContext: "default", | ||||
| 		Env:         "default", | ||||
| 		Logger:      logger, | ||||
| 		Namespace:   "testNamespace", | ||||
| 	}, files) | ||||
| 	out := captureStdout(func() { | ||||
| 		err := app.PrintState(configImpl{}) | ||||
| 		assert.NilError(t, err) | ||||
| 	}) | ||||
| 	assert.Assert(t, strings.Count(out, "---") == 1, | ||||
| 		"state should contain '---' yaml doc separator:\n%s\n", out) | ||||
| 	assert.Assert(t, strings.Contains(out, "helmfile.yaml"), | ||||
| 		"state should contain source helmfile name:\n%s\n", out) | ||||
| 	assert.Assert(t, strings.Contains(out, "name: myrelease1"), | ||||
| 		"state should contain releases:\n%s\n", out) | ||||
| } | ||||
| 
 | ||||
| func TestPrint_MultiStateFile(t *testing.T) { | ||||
| 	files := map[string]string{ | ||||
| 		"/path/to/helmfile.d/first.yaml": ` | ||||
| releases: | ||||
| - name: myrelease1 | ||||
|   chart: mychart1 | ||||
| - name: myrelease2 | ||||
|   chart: mychart1 | ||||
| `, | ||||
| 		"/path/to/helmfile.d/second.yaml": ` | ||||
| releases: | ||||
| - name: myrelease3 | ||||
|   chart: mychart1 | ||||
| - name: myrelease4 | ||||
|   chart: mychart1 | ||||
| `, | ||||
| 	} | ||||
| 	stdout := os.Stdout | ||||
| 	defer func() { os.Stdout = stdout }() | ||||
| 
 | ||||
| 	var buffer bytes.Buffer | ||||
| 	logger := helmexec.NewLogger(&buffer, "debug") | ||||
| 
 | ||||
| 	app := appWithFs(&App{ | ||||
| 		glob:        filepath.Glob, | ||||
| 		abs:         filepath.Abs, | ||||
| 		KubeContext: "default", | ||||
| 		Env:         "default", | ||||
| 		Logger:      logger, | ||||
| 		Namespace:   "testNamespace", | ||||
| 	}, files) | ||||
| 	out := captureStdout(func() { | ||||
| 		err := app.PrintState(configImpl{}) | ||||
| 		assert.NilError(t, err) | ||||
| 	}) | ||||
| 	assert.Assert(t, strings.Count(out, "---") == 2, | ||||
| 		"state should contain '---' yaml doc separators:\n%s\n", out) | ||||
| 	assert.Assert(t, strings.Contains(out, "second.yaml"), | ||||
| 		"state should contain source helmfile name:\n%s\n", out) | ||||
| 	assert.Assert(t, strings.Contains(out, "second.yaml"), | ||||
| 		"state should contain source helmfile name:\n%s\n", out) | ||||
| } | ||||
| 
 | ||||
| func TestList(t *testing.T) { | ||||
| 	files := map[string]string{ | ||||
| 		"/path/to/helmfile.d/first.yaml": ` | ||||
| releases: | ||||
| - name: myrelease1 | ||||
|   chart: mychart1 | ||||
|   installed: no | ||||
|   labels: | ||||
|     id: myrelease1 | ||||
| - name: myrelease2 | ||||
|   chart: mychart1 | ||||
| `, | ||||
| 		"/path/to/helmfile.d/second.yaml": ` | ||||
| releases: | ||||
| - name: myrelease3 | ||||
|   chart: mychart1 | ||||
|   installed: yes | ||||
| - name: myrelease4 | ||||
|   chart: mychart1 | ||||
|   labels: | ||||
|     id: myrelease1 | ||||
| `, | ||||
| 	} | ||||
| 	stdout := os.Stdout | ||||
| 	defer func() { os.Stdout = stdout }() | ||||
| 
 | ||||
| 	var buffer bytes.Buffer | ||||
| 	logger := helmexec.NewLogger(&buffer, "debug") | ||||
| 
 | ||||
| 	app := appWithFs(&App{ | ||||
| 		glob:        filepath.Glob, | ||||
| 		abs:         filepath.Abs, | ||||
| 		KubeContext: "default", | ||||
| 		Env:         "default", | ||||
| 		Logger:      logger, | ||||
| 		Namespace:   "testNamespace", | ||||
| 	}, files) | ||||
| 	out := captureStdout(func() { | ||||
| 		err := app.ListReleases(configImpl{}) | ||||
| 		assert.NilError(t, err) | ||||
| 	}) | ||||
| 
 | ||||
| 	expected := `NAME      	NAMESPACE	INSTALLED	LABELS        | ||||
| myrelease1	         	false    	id:myrelease1 | ||||
| myrelease2	         	true     	              | ||||
| myrelease3	         	true     	              | ||||
| myrelease4	         	true     	id:myrelease1 | ||||
| ` | ||||
| 	assert.Equal(t, expected, out) | ||||
| } | ||||
|  |  | |||
|  | @ -120,6 +120,9 @@ type StatusesConfigProvider interface { | |||
| 	concurrencyConfig | ||||
| } | ||||
| 
 | ||||
| type StateConfigProvider interface { | ||||
| } | ||||
| 
 | ||||
| type concurrencyConfig interface { | ||||
| 	Concurrency() int | ||||
| } | ||||
|  |  | |||
|  | @ -2,10 +2,11 @@ package app | |||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/roboll/helmfile/pkg/argparser" | ||||
| 	"github.com/roboll/helmfile/pkg/helmexec" | ||||
| 	"github.com/roboll/helmfile/pkg/state" | ||||
| 	"strings" | ||||
| ) | ||||
| 
 | ||||
| type Run struct { | ||||
|  |  | |||
|  | @ -90,6 +90,13 @@ func (helm *execer) UpdateRepo() error { | |||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) BuildDeps(name, chart string) error { | ||||
| 	helm.logger.Infof("Building dependency release=%v, chart=%v", name, chart) | ||||
| 	out, err := helm.exec([]string{"dependency", "build", chart}, map[string]string{}) | ||||
| 	helm.info(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) UpdateDeps(chart string) error { | ||||
| 	helm.logger.Infof("Updating dependency %v", chart) | ||||
| 	out, err := helm.exec([]string{"dependency", "update", chart}, map[string]string{}) | ||||
|  | @ -97,15 +104,8 @@ func (helm *execer) UpdateDeps(chart string) error { | |||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) BuildDeps(chart string) error { | ||||
| 	helm.logger.Infof("Building dependency %v", chart) | ||||
| 	out, err := helm.exec([]string{"dependency", "build", chart}, map[string]string{}) | ||||
| 	helm.info(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) SyncRelease(context HelmContext, name, chart string, flags ...string) error { | ||||
| 	helm.logger.Infof("Upgrading %v", chart) | ||||
| 	helm.logger.Infof("Upgrading release=%v, chart=%v", name, chart) | ||||
| 	preArgs := context.GetTillerlessArgs(helm.helmBinary) | ||||
| 	env := context.getTillerlessEnv() | ||||
| 	out, err := helm.exec(append(append(preArgs, "upgrade", "--install", "--reset-values", name, chart), flags...), env) | ||||
|  | @ -199,14 +199,15 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str | |||
| 	return tmpFile.Name(), err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) TemplateRelease(chart string, flags ...string) error { | ||||
| 	out, err := helm.exec(append([]string{"template", chart}, flags...), map[string]string{}) | ||||
| func (helm *execer) TemplateRelease(name string, chart string, flags ...string) error { | ||||
| 	helm.logger.Infof("Templating release=%v, chart=%v", name, chart) | ||||
| 	out, err := helm.exec(append([]string{"template", chart, "--name", name}, flags...), map[string]string{}) | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) DiffRelease(context HelmContext, name, chart string, flags ...string) error { | ||||
| 	helm.logger.Infof("Comparing %v %v", name, chart) | ||||
| 	helm.logger.Infof("Comparing release=%v, chart=%v", name, chart) | ||||
| 	preArgs := context.GetTillerlessArgs(helm.helmBinary) | ||||
| 	env := context.getTillerlessEnv() | ||||
| 	out, err := helm.exec(append(append(preArgs, "diff", "upgrade", "--reset-values", "--allow-unreleased", name, chart), flags...), env) | ||||
|  | @ -233,8 +234,8 @@ func (helm *execer) DiffRelease(context HelmContext, name, chart string, flags . | |||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func (helm *execer) Lint(chart string, flags ...string) error { | ||||
| 	helm.logger.Infof("Linting %v", chart) | ||||
| func (helm *execer) Lint(name, chart string, flags ...string) error { | ||||
| 	helm.logger.Infof("Linting release=%v, chart=%v", name, chart) | ||||
| 	out, err := helm.exec(append([]string{"lint", chart}, flags...), map[string]string{}) | ||||
| 	helm.write(out) | ||||
| 	return err | ||||
|  |  | |||
|  | @ -135,7 +135,7 @@ func Test_SyncRelease(t *testing.T) { | |||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	helm.SyncRelease(HelmContext{}, "release", "chart", "--timeout 10", "--wait") | ||||
| 	expected := `Upgrading chart | ||||
| 	expected := `Upgrading release=release, chart=chart | ||||
| exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev | ||||
| exec: helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev:  | ||||
| ` | ||||
|  | @ -145,7 +145,7 @@ exec: helm upgrade --install --reset-values release chart --timeout 10 --wait -- | |||
| 
 | ||||
| 	buffer.Reset() | ||||
| 	helm.SyncRelease(HelmContext{}, "release", "chart") | ||||
| 	expected = `Upgrading chart | ||||
| 	expected = `Upgrading release=release, chart=chart | ||||
| exec: helm upgrade --install --reset-values release chart --kube-context dev | ||||
| exec: helm upgrade --install --reset-values release chart --kube-context dev:  | ||||
| ` | ||||
|  | @ -160,7 +160,7 @@ func Test_SyncReleaseTillerless(t *testing.T) { | |||
| 	helm := MockExecer(logger, "dev") | ||||
| 	helm.SyncRelease(HelmContext{Tillerless: true, TillerNamespace: "foo"}, "release", "chart", | ||||
| 		"--timeout 10", "--wait") | ||||
| 	expected := `Upgrading chart | ||||
| 	expected := `Upgrading release=release, chart=chart | ||||
| exec: helm tiller run foo -- helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev | ||||
| exec: helm tiller run foo -- helm upgrade --install --reset-values release chart --timeout 10 --wait --kube-context dev:  | ||||
| ` | ||||
|  | @ -198,8 +198,8 @@ func Test_BuildDeps(t *testing.T) { | |||
| 	var buffer bytes.Buffer | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	helm.BuildDeps("./chart/foo") | ||||
| 	expected := `Building dependency ./chart/foo | ||||
| 	helm.BuildDeps("foo", "./chart/foo") | ||||
| 	expected := `Building dependency release=foo, chart=./chart/foo | ||||
| exec: helm dependency build ./chart/foo --kube-context dev | ||||
| exec: helm dependency build ./chart/foo --kube-context dev:  | ||||
| ` | ||||
|  | @ -209,8 +209,8 @@ exec: helm dependency build ./chart/foo --kube-context dev: | |||
| 
 | ||||
| 	buffer.Reset() | ||||
| 	helm.SetExtraArgs("--verify") | ||||
| 	helm.BuildDeps("./chart/foo") | ||||
| 	expected = `Building dependency ./chart/foo | ||||
| 	helm.BuildDeps("foo", "./chart/foo") | ||||
| 	expected = `Building dependency release=foo, chart=./chart/foo | ||||
| exec: helm dependency build ./chart/foo --verify --kube-context dev | ||||
| exec: helm dependency build ./chart/foo --verify --kube-context dev:  | ||||
| ` | ||||
|  | @ -248,7 +248,7 @@ func Test_DiffRelease(t *testing.T) { | |||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	helm.DiffRelease(HelmContext{}, "release", "chart", "--timeout 10", "--wait") | ||||
| 	expected := `Comparing release chart | ||||
| 	expected := `Comparing release=release, chart=chart | ||||
| exec: helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --kube-context dev | ||||
| exec: helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --kube-context dev:  | ||||
| ` | ||||
|  | @ -258,7 +258,7 @@ exec: helm diff upgrade --reset-values --allow-unreleased release chart --timeou | |||
| 
 | ||||
| 	buffer.Reset() | ||||
| 	helm.DiffRelease(HelmContext{}, "release", "chart") | ||||
| 	expected = `Comparing release chart | ||||
| 	expected = `Comparing release=release, chart=chart | ||||
| exec: helm diff upgrade --reset-values --allow-unreleased release chart --kube-context dev | ||||
| exec: helm diff upgrade --reset-values --allow-unreleased release chart --kube-context dev:  | ||||
| ` | ||||
|  | @ -272,7 +272,7 @@ func Test_DiffReleaseTillerless(t *testing.T) { | |||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	helm.DiffRelease(HelmContext{Tillerless: true}, "release", "chart", "--timeout 10", "--wait") | ||||
| 	expected := `Comparing release chart | ||||
| 	expected := `Comparing release=release, chart=chart | ||||
| exec: helm tiller run -- helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --kube-context dev | ||||
| exec: helm tiller run -- helm diff upgrade --reset-values --allow-unreleased release chart --timeout 10 --wait --kube-context dev:  | ||||
| ` | ||||
|  | @ -413,8 +413,8 @@ func Test_Lint(t *testing.T) { | |||
| 	var buffer bytes.Buffer | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	helm.Lint("path/to/chart", "--values", "file.yml") | ||||
| 	expected := `Linting path/to/chart | ||||
| 	helm.Lint("release", "path/to/chart", "--values", "file.yml") | ||||
| 	expected := `Linting release=release, chart=path/to/chart | ||||
| exec: helm lint path/to/chart --values file.yml --kube-context dev | ||||
| exec: helm lint path/to/chart --values file.yml --kube-context dev:  | ||||
| ` | ||||
|  | @ -498,9 +498,10 @@ func Test_Template(t *testing.T) { | |||
| 	var buffer bytes.Buffer | ||||
| 	logger := NewLogger(&buffer, "debug") | ||||
| 	helm := MockExecer(logger, "dev") | ||||
| 	helm.TemplateRelease("path/to/chart", "--values", "file.yml") | ||||
| 	expected := `exec: helm template path/to/chart --values file.yml --kube-context dev | ||||
| exec: helm template path/to/chart --values file.yml --kube-context dev:  | ||||
| 	helm.TemplateRelease("release", "path/to/chart", "--values", "file.yml") | ||||
| 	expected := `Templating release=release, chart=path/to/chart | ||||
| exec: helm template path/to/chart --name release --values file.yml --kube-context dev | ||||
| exec: helm template path/to/chart --name release --values file.yml --kube-context dev:  | ||||
| ` | ||||
| 	if buffer.String() != expected { | ||||
| 		t.Errorf("helmexec.Template()\nactual = %v\nexpect = %v", buffer.String(), expected) | ||||
|  |  | |||
|  | @ -7,13 +7,13 @@ type Interface interface { | |||
| 
 | ||||
| 	AddRepo(name, repository, certfile, keyfile, username, password string) error | ||||
| 	UpdateRepo() error | ||||
| 	BuildDeps(chart string) error | ||||
| 	BuildDeps(name, chart string) error | ||||
| 	UpdateDeps(chart string) error | ||||
| 	SyncRelease(context HelmContext, name, chart string, flags ...string) error | ||||
| 	DiffRelease(context HelmContext, name, chart string, flags ...string) error | ||||
| 	TemplateRelease(chart string, flags ...string) error | ||||
| 	TemplateRelease(name, chart string, flags ...string) error | ||||
| 	Fetch(chart string, flags ...string) error | ||||
| 	Lint(chart string, flags ...string) error | ||||
| 	Lint(name, chart string, flags ...string) error | ||||
| 	ReleaseStatus(context HelmContext, name string, flags ...string) error | ||||
| 	DeleteRelease(context HelmContext, name string, flags ...string) error | ||||
| 	TestRelease(context HelmContext, name string, flags ...string) error | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| package state | ||||
| 
 | ||||
| type EnvironmentSpec struct { | ||||
| 	Values  []interface{} `yaml:"values"` | ||||
| 	Secrets []string      `yaml:"secrets"` | ||||
| 	Values  []interface{} `yaml:"values,omitempty"` | ||||
| 	Secrets []string      `yaml:"secrets,omitempty"` | ||||
| 
 | ||||
| 	// MissingFileHandler instructs helmfile to fail when unable to find a environment values file listed
 | ||||
| 	// under `environments.NAME.values`.
 | ||||
|  | @ -11,5 +11,5 @@ type EnvironmentSpec struct { | |||
| 	//
 | ||||
| 	// Use "Warn", "Info", or "Debug" if you want helmfile to not fail when a values file is missing, while just leaving
 | ||||
| 	// a message about the missing file at the log-level.
 | ||||
| 	MissingFileHandler *string `yaml:"missingFileHandler"` | ||||
| 	MissingFileHandler *string `yaml:"missingFileHandler,omitempty"` | ||||
| } | ||||
|  |  | |||
|  | @ -48,6 +48,51 @@ func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*R | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if result.WaitTemplate != nil { | ||||
| 		ts := *result.WaitTemplate | ||||
| 		resultTmpl, err := renderer.RenderTemplateContentToString([]byte(ts)) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("failed executing template expressions in release \"%s\".version = \"%s\": %v", r.Name, ts, err) | ||||
| 		} | ||||
| 		result.WaitTemplate = &resultTmpl | ||||
| 	} | ||||
| 
 | ||||
| 	if result.InstalledTemplate != nil { | ||||
| 		ts := *result.InstalledTemplate | ||||
| 		resultTmpl, err := renderer.RenderTemplateContentToString([]byte(ts)) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("failed executing template expressions in release \"%s\".version = \"%s\": %v", r.Name, ts, err) | ||||
| 		} | ||||
| 		result.InstalledTemplate = &resultTmpl | ||||
| 	} | ||||
| 
 | ||||
| 	if result.TillerlessTemplate != nil { | ||||
| 		ts := *result.TillerlessTemplate | ||||
| 		resultTmpl, err := renderer.RenderTemplateContentToString([]byte(ts)) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("failed executing template expressions in release \"%s\".version = \"%s\": %v", r.Name, ts, err) | ||||
| 		} | ||||
| 		result.TillerlessTemplate = &resultTmpl | ||||
| 	} | ||||
| 
 | ||||
| 	if result.VerifyTemplate != nil { | ||||
| 		ts := *result.VerifyTemplate | ||||
| 		resultTmpl, err := renderer.RenderTemplateContentToString([]byte(ts)) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("failed executing template expressions in release \"%s\".version = \"%s\": %v", r.Name, ts, err) | ||||
| 		} | ||||
| 		result.VerifyTemplate = &resultTmpl | ||||
| 	} | ||||
| 
 | ||||
| 	for key, val := range result.Labels { | ||||
| 		ts := val | ||||
| 		s, err := renderer.RenderTemplateContentToBuffer([]byte(ts)) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("failed executing template expressions in release \"%s\".labels[%s] = \"%s\": %v", r.Name, key, ts, err) | ||||
| 		} | ||||
| 		result.Labels[key] = s.String() | ||||
| 	} | ||||
| 
 | ||||
| 	for i, t := range result.Values { | ||||
| 		switch ts := t.(type) { | ||||
| 		case string: | ||||
|  | @ -56,6 +101,24 @@ func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*R | |||
| 				return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%s\": %v", r.Name, i, ts, err) | ||||
| 			} | ||||
| 			result.Values[i] = s.String() | ||||
| 		case map[interface{}]interface{}: | ||||
| 			serialized, err := yaml.Marshal(ts) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%v\": %v", r.Name, i, ts, err) | ||||
| 			} | ||||
| 
 | ||||
| 			s, err := renderer.RenderTemplateContentToBuffer([]byte(serialized)) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%v\": %v", r.Name, i, serialized, err) | ||||
| 			} | ||||
| 
 | ||||
| 			var deserialized map[interface{}]interface{} | ||||
| 
 | ||||
| 			if err := yaml.Unmarshal(s.Bytes(), &deserialized); err != nil { | ||||
| 				return nil, fmt.Errorf("failed executing template expressions in release \"%s\".values[%d] = \"%v\": %v", r.Name, i, ts, err) | ||||
| 			} | ||||
| 
 | ||||
| 			result.Values[i] = deserialized | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
|  | @ -67,6 +130,44 @@ func (r ReleaseSpec) ExecuteTemplateExpressions(renderer *tmpl.FileRenderer) (*R | |||
| 		result.Secrets[i] = s.String() | ||||
| 	} | ||||
| 
 | ||||
| 	for i, val := range result.SetValues { | ||||
| 		{ | ||||
| 			// name
 | ||||
| 			ts := val.Name | ||||
| 			s, err := renderer.RenderTemplateContentToBuffer([]byte(ts)) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("failed executing template expressions in release \"%s\".set[%d].name = \"%s\": %v", r.Name, i, ts, err) | ||||
| 			} | ||||
| 			result.SetValues[i].Name = s.String() | ||||
| 		} | ||||
| 		{ | ||||
| 			// value
 | ||||
| 			ts := val.Value | ||||
| 			s, err := renderer.RenderTemplateContentToBuffer([]byte(ts)) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("failed executing template expressions in release \"%s\".set[%d].value = \"%s\": %v", r.Name, i, ts, err) | ||||
| 			} | ||||
| 			result.SetValues[i].Value = s.String() | ||||
| 		} | ||||
| 		{ | ||||
| 			// file
 | ||||
| 			ts := val.File | ||||
| 			s, err := renderer.RenderTemplateContentToBuffer([]byte(ts)) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("failed executing template expressions in release \"%s\".set[%d].file = \"%s\": %v", r.Name, i, ts, err) | ||||
| 			} | ||||
| 			result.SetValues[i].File = s.String() | ||||
| 		} | ||||
| 		for j, ts := range val.Values { | ||||
| 			// values
 | ||||
| 			s, err := renderer.RenderTemplateContentToBuffer([]byte(ts)) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("failed executing template expressions in release \"%s\".set[%d].values[%d] = \"%s\": %v", r.Name, i, j, ts, err) | ||||
| 			} | ||||
| 			result.SetValues[i].Values[j] = s.String() | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return result, nil | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -34,23 +34,23 @@ type HelmState struct { | |||
| 	FilePath string | ||||
| 
 | ||||
| 	// DefaultValues is the default values to be overrode by environment values and command-line overrides
 | ||||
| 	DefaultValues []interface{} `yaml:"values"` | ||||
| 	DefaultValues []interface{} `yaml:"values,omitempty"` | ||||
| 
 | ||||
| 	Environments map[string]EnvironmentSpec `yaml:"environments"` | ||||
| 	Environments map[string]EnvironmentSpec `yaml:"environments,omitempty"` | ||||
| 
 | ||||
| 	Bases              []string          `yaml:"bases"` | ||||
| 	HelmDefaults       HelmSpec          `yaml:"helmDefaults"` | ||||
| 	Helmfiles          []SubHelmfileSpec `yaml:"helmfiles"` | ||||
| 	DeprecatedContext  string            `yaml:"context"` | ||||
| 	DeprecatedReleases []ReleaseSpec     `yaml:"charts"` | ||||
| 	Namespace          string            `yaml:"namespace"` | ||||
| 	Repositories       []RepositorySpec  `yaml:"repositories"` | ||||
| 	Releases           []ReleaseSpec     `yaml:"releases"` | ||||
| 	Selectors          []string | ||||
| 	Bases              []string          `yaml:"bases,omitempty"` | ||||
| 	HelmDefaults       HelmSpec          `yaml:"helmDefaults,omitempty"` | ||||
| 	Helmfiles          []SubHelmfileSpec `yaml:"helmfiles,omitempty"` | ||||
| 	DeprecatedContext  string            `yaml:"context,omitempty"` | ||||
| 	DeprecatedReleases []ReleaseSpec     `yaml:"charts,omitempty"` | ||||
| 	Namespace          string            `yaml:"namespace,omitempty"` | ||||
| 	Repositories       []RepositorySpec  `yaml:"repositories,omitempty"` | ||||
| 	Releases           []ReleaseSpec     `yaml:"releases,omitempty"` | ||||
| 	Selectors          []string          `yaml:"-"` | ||||
| 
 | ||||
| 	Templates map[string]TemplateSpec `yaml:"templates"` | ||||
| 
 | ||||
| 	Env environment.Environment | ||||
| 	Env environment.Environment `yaml:"-"` | ||||
| 
 | ||||
| 	logger *zap.SugaredLogger | ||||
| 
 | ||||
|  | @ -67,23 +67,26 @@ type HelmState struct { | |||
| 
 | ||||
| // SubHelmfileSpec defines the subhelmfile path and options
 | ||||
| type SubHelmfileSpec struct { | ||||
| 	Path               string   //path or glob pattern for the sub helmfiles
 | ||||
| 	Selectors          []string //chosen selectors for the sub helmfiles
 | ||||
| 	SelectorsInherited bool     //do the sub helmfiles inherits from parent selectors
 | ||||
| 	//path or glob pattern for the sub helmfiles
 | ||||
| 	Path string `yaml:"path,omitempty"` | ||||
| 	//chosen selectors for the sub helmfiles
 | ||||
| 	Selectors []string `yaml:"selectors,omitempty"` | ||||
| 	//do the sub helmfiles inherits from parent selectors
 | ||||
| 	SelectorsInherited bool `yaml:"selectorsInherited,omitempty"` | ||||
| 
 | ||||
| 	Environment SubhelmfileEnvironmentSpec | ||||
| } | ||||
| 
 | ||||
| type SubhelmfileEnvironmentSpec struct { | ||||
| 	OverrideValues []interface{} `yaml:"values"` | ||||
| 	OverrideValues []interface{} `yaml:"values,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // HelmSpec to defines helmDefault values
 | ||||
| type HelmSpec struct { | ||||
| 	KubeContext     string   `yaml:"kubeContext"` | ||||
| 	TillerNamespace string   `yaml:"tillerNamespace"` | ||||
| 	KubeContext     string   `yaml:"kubeContext,omitempty"` | ||||
| 	TillerNamespace string   `yaml:"tillerNamespace,omitempty"` | ||||
| 	Tillerless      bool     `yaml:"tillerless"` | ||||
| 	Args            []string `yaml:"args"` | ||||
| 	Args            []string `yaml:"args,omitempty"` | ||||
| 	Verify          bool     `yaml:"verify"` | ||||
| 	// Devel, when set to true, use development versions, too. Equivalent to version '>0.0.0-0'
 | ||||
| 	Devel bool `yaml:"devel"` | ||||
|  | @ -99,77 +102,83 @@ type HelmSpec struct { | |||
| 	Atomic bool `yaml:"atomic"` | ||||
| 
 | ||||
| 	TLS       bool   `yaml:"tls"` | ||||
| 	TLSCACert string `yaml:"tlsCACert"` | ||||
| 	TLSKey    string `yaml:"tlsKey"` | ||||
| 	TLSCert   string `yaml:"tlsCert"` | ||||
| 	TLSCACert string `yaml:"tlsCACert,omitempty"` | ||||
| 	TLSKey    string `yaml:"tlsKey,omitempty"` | ||||
| 	TLSCert   string `yaml:"tlsCert,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // RepositorySpec that defines values for a helm repo
 | ||||
| type RepositorySpec struct { | ||||
| 	Name     string `yaml:"name"` | ||||
| 	URL      string `yaml:"url"` | ||||
| 	CertFile string `yaml:"certFile"` | ||||
| 	KeyFile  string `yaml:"keyFile"` | ||||
| 	Username string `yaml:"username"` | ||||
| 	Password string `yaml:"password"` | ||||
| 	Name     string `yaml:"name,omitempty"` | ||||
| 	URL      string `yaml:"url,omitempty"` | ||||
| 	CertFile string `yaml:"certFile,omitempty"` | ||||
| 	KeyFile  string `yaml:"keyFile,omitempty"` | ||||
| 	Username string `yaml:"username,omitempty"` | ||||
| 	Password string `yaml:"password,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // ReleaseSpec defines the structure of a helm release
 | ||||
| type ReleaseSpec struct { | ||||
| 	// Chart is the name of the chart being installed to create this release
 | ||||
| 	Chart   string `yaml:"chart"` | ||||
| 	Version string `yaml:"version"` | ||||
| 	Verify  *bool  `yaml:"verify"` | ||||
| 	Chart   string `yaml:"chart,omitempty"` | ||||
| 	Version string `yaml:"version,omitempty"` | ||||
| 	Verify  *bool  `yaml:"verify,omitempty"` | ||||
| 	// Devel, when set to true, use development versions, too. Equivalent to version '>0.0.0-0'
 | ||||
| 	Devel *bool `yaml:"devel"` | ||||
| 	Devel *bool `yaml:"devel,omitempty"` | ||||
| 	// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
 | ||||
| 	Wait *bool `yaml:"wait"` | ||||
| 	Wait *bool `yaml:"wait,omitempty"` | ||||
| 	// Timeout is the time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300)
 | ||||
| 	Timeout *int `yaml:"timeout"` | ||||
| 	Timeout *int `yaml:"timeout,omitempty"` | ||||
| 	// RecreatePods, when set to true, instruct helmfile to perform pods restart for the resource if applicable
 | ||||
| 	RecreatePods *bool `yaml:"recreatePods"` | ||||
| 	RecreatePods *bool `yaml:"recreatePods,omitempty"` | ||||
| 	// Force, when set to true, forces resource update through delete/recreate if needed
 | ||||
| 	Force *bool `yaml:"force"` | ||||
| 	Force *bool `yaml:"force,omitempty"` | ||||
| 	// Installed, when set to true, `delete --purge` the release
 | ||||
| 	Installed *bool `yaml:"installed"` | ||||
| 	Installed *bool `yaml:"installed,omitempty"` | ||||
| 	// Atomic, when set to true, restore previous state in case of a failed install/upgrade attempt
 | ||||
| 	Atomic *bool `yaml:"atomic"` | ||||
| 	Atomic *bool `yaml:"atomic,omitempty"` | ||||
| 
 | ||||
| 	// MissingFileHandler is set to either "Error" or "Warn". "Error" instructs helmfile to fail when unable to find a values or secrets file. When "Warn", it prints the file and continues.
 | ||||
| 	// The default value for MissingFileHandler is "Error".
 | ||||
| 	MissingFileHandler *string `yaml:"missingFileHandler"` | ||||
| 	MissingFileHandler *string `yaml:"missingFileHandler,omitempty"` | ||||
| 
 | ||||
| 	// Hooks is a list of extension points paired with operations, that are executed in specific points of the lifecycle of releases defined in helmfile
 | ||||
| 	Hooks []event.Hook `yaml:"hooks"` | ||||
| 	Hooks []event.Hook `yaml:"hooks,omitempty"` | ||||
| 
 | ||||
| 	// Name is the name of this release
 | ||||
| 	Name      string            `yaml:"name"` | ||||
| 	Namespace string            `yaml:"namespace"` | ||||
| 	Labels    map[string]string `yaml:"labels"` | ||||
| 	Values    []interface{}     `yaml:"values"` | ||||
| 	Secrets   []string          `yaml:"secrets"` | ||||
| 	SetValues []SetValue        `yaml:"set"` | ||||
| 	Name      string            `yaml:"name,omitempty"` | ||||
| 	Namespace string            `yaml:"namespace,omitempty"` | ||||
| 	Labels    map[string]string `yaml:"labels,omitempty"` | ||||
| 	Values    []interface{}     `yaml:"values,omitempty"` | ||||
| 	Secrets   []string          `yaml:"secrets,omitempty"` | ||||
| 	SetValues []SetValue        `yaml:"set,omitempty"` | ||||
| 
 | ||||
| 	// The 'env' section is not really necessary any longer, as 'set' would now provide the same functionality
 | ||||
| 	EnvValues []SetValue `yaml:"env"` | ||||
| 	EnvValues []SetValue `yaml:"env,omitempty"` | ||||
| 
 | ||||
| 	ValuesPathPrefix string `yaml:"valuesPathPrefix"` | ||||
| 	ValuesPathPrefix string `yaml:"valuesPathPrefix,omitempty"` | ||||
| 
 | ||||
| 	TillerNamespace string `yaml:"tillerNamespace"` | ||||
| 	Tillerless      *bool  `yaml:"tillerless"` | ||||
| 	TillerNamespace string `yaml:"tillerNamespace,omitempty"` | ||||
| 	Tillerless      *bool  `yaml:"tillerless,omitempty"` | ||||
| 
 | ||||
| 	KubeContext string `yaml:"kubeContext"` | ||||
| 	KubeContext string `yaml:"kubeContext,omitempty"` | ||||
| 
 | ||||
| 	TLS       *bool  `yaml:"tls"` | ||||
| 	TLSCACert string `yaml:"tlsCACert"` | ||||
| 	TLSKey    string `yaml:"tlsKey"` | ||||
| 	TLSCert   string `yaml:"tlsCert"` | ||||
| 	TLS       *bool  `yaml:"tls,omitempty"` | ||||
| 	TLSCACert string `yaml:"tlsCACert,omitempty"` | ||||
| 	TLSKey    string `yaml:"tlsKey,omitempty"` | ||||
| 	TLSCert   string `yaml:"tlsCert,omitempty"` | ||||
| 
 | ||||
| 	// These values are used in templating
 | ||||
| 	TillerlessTemplate *string `yaml:"tillerlessTemplate,omitempty"` | ||||
| 	VerifyTemplate     *string `yaml:"verifyTemplate,omitempty"` | ||||
| 	WaitTemplate       *string `yaml:"waitTemplate,omitempty"` | ||||
| 	InstalledTemplate  *string `yaml:"installedTemplate,omitempty"` | ||||
| 
 | ||||
| 	// These settings requires helm-x integration to work
 | ||||
| 	Dependencies          []Dependency  `yaml:"dependencies"` | ||||
| 	JSONPatches           []interface{} `yaml:"jsonPatches"` | ||||
| 	StrategicMergePatches []interface{} `yaml:"strategicMergePatches"` | ||||
| 	Adopt                 []string      `yaml:"adopt"` | ||||
| 	Dependencies          []Dependency  `yaml:"dependencies,omitempty"` | ||||
| 	JSONPatches           []interface{} `yaml:"jsonPatches,omitempty"` | ||||
| 	StrategicMergePatches []interface{} `yaml:"strategicMergePatches,omitempty"` | ||||
| 	Adopt                 []string      `yaml:"adopt,omitempty"` | ||||
| 
 | ||||
| 	// generatedValues are values that need cleaned up on exit
 | ||||
| 	generatedValues []string | ||||
|  | @ -179,10 +188,10 @@ type ReleaseSpec struct { | |||
| 
 | ||||
| // SetValue are the key values to set on a helm release
 | ||||
| type SetValue struct { | ||||
| 	Name   string   `yaml:"name"` | ||||
| 	Value  string   `yaml:"value"` | ||||
| 	File   string   `yaml:"file"` | ||||
| 	Values []string `yaml:"values"` | ||||
| 	Name   string   `yaml:"name,omitempty"` | ||||
| 	Value  string   `yaml:"value,omitempty"` | ||||
| 	File   string   `yaml:"file,omitempty"` | ||||
| 	Values []string `yaml:"values,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // AffectedReleases hold the list of released that where updated, deleted, or in error
 | ||||
|  | @ -601,7 +610,7 @@ func (st *HelmState) TemplateReleases(helm helmexec.Interface, outputDir string, | |||
| 		} | ||||
| 
 | ||||
| 		if len(errs) == 0 { | ||||
| 			if err := helm.TemplateRelease(temp[release.Name], flags...); err != nil { | ||||
| 			if err := helm.TemplateRelease(release.Name, temp[release.Name], flags...); err != nil { | ||||
| 				errs = append(errs, err) | ||||
| 			} | ||||
| 		} | ||||
|  | @ -666,7 +675,7 @@ func (st *HelmState) LintReleases(helm helmexec.Interface, additionalValues []st | |||
| 		} | ||||
| 
 | ||||
| 		if len(errs) == 0 { | ||||
| 			if err := helm.Lint(temp[release.Name], flags...); err != nil { | ||||
| 			if err := helm.Lint(release.Name, temp[release.Name], flags...); err != nil { | ||||
| 				errs = append(errs, err) | ||||
| 			} | ||||
| 		} | ||||
|  | @ -1051,7 +1060,7 @@ func (st *HelmState) ResolveDeps() (*HelmState, error) { | |||
| 
 | ||||
| // UpdateDeps wrapper for updating dependencies on the releases
 | ||||
| func (st *HelmState) UpdateDeps(helm helmexec.Interface) []error { | ||||
| 	errs := []error{} | ||||
| 	var errs []error | ||||
| 
 | ||||
| 	for _, release := range st.Releases { | ||||
| 		if isLocalChart(release.Chart) { | ||||
|  | @ -1084,7 +1093,7 @@ func (st *HelmState) BuildDeps(helm helmexec.Interface) []error { | |||
| 
 | ||||
| 	for _, release := range st.Releases { | ||||
| 		if isLocalChart(release.Chart) { | ||||
| 			if err := helm.BuildDeps(normalizeChart(st.basePath, release.Chart)); err != nil { | ||||
| 			if err := helm.BuildDeps(release.Name, normalizeChart(st.basePath, release.Chart)); err != nil { | ||||
| 				errs = append(errs, err) | ||||
| 			} | ||||
| 		} | ||||
|  | @ -1594,3 +1603,11 @@ func (st *HelmState) GenerateOutputDir(outputDir string, release ReleaseSpec) (s | |||
| 
 | ||||
| 	return path.Join(outputDir, sb.String()), nil | ||||
| } | ||||
| 
 | ||||
| func (st *HelmState) ToYaml() (string, error) { | ||||
| 	if result, err := yaml.Marshal(st); err != nil { | ||||
| 		return "", err | ||||
| 	} else { | ||||
| 		return string(result), nil | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -2,9 +2,12 @@ package state | |||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 
 | ||||
| 	"github.com/imdario/mergo" | ||||
| 	"github.com/roboll/helmfile/pkg/maputil" | ||||
| 	"github.com/roboll/helmfile/pkg/tmpl" | ||||
| 	"gopkg.in/yaml.v2" | ||||
| ) | ||||
| 
 | ||||
| func (st *HelmState) Values() (map[string]interface{}, error) { | ||||
|  | @ -41,6 +44,55 @@ func (st *HelmState) valuesFileTemplateData() EnvironmentTemplateData { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| func getBoolRefFromStringTemplate(templateRef string) (*bool, error) { | ||||
| 	var result bool | ||||
| 	if err := yaml.Unmarshal([]byte(templateRef), &result); err != nil { | ||||
| 		return nil, fmt.Errorf("failed deserialising string %s: %v", templateRef, err) | ||||
| 	} | ||||
| 	return &result, nil | ||||
| } | ||||
| 
 | ||||
| func updateBoolTemplatedValues(r *ReleaseSpec) error { | ||||
| 
 | ||||
| 	if r.InstalledTemplate != nil { | ||||
| 		if installed, err := getBoolRefFromStringTemplate(*r.InstalledTemplate); err != nil { | ||||
| 			return fmt.Errorf("installedTemplate: %v", err) | ||||
| 		} else { | ||||
| 			r.InstalledTemplate = nil | ||||
| 			r.Installed = installed | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if r.WaitTemplate != nil { | ||||
| 		if wait, err := getBoolRefFromStringTemplate(*r.WaitTemplate); err != nil { | ||||
| 			return fmt.Errorf("waitTemplate: %v", err) | ||||
| 		} else { | ||||
| 			r.WaitTemplate = nil | ||||
| 			r.Wait = wait | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if r.TillerlessTemplate != nil { | ||||
| 		if tillerless, err := getBoolRefFromStringTemplate(*r.TillerlessTemplate); err != nil { | ||||
| 			return fmt.Errorf("tillerlessTemplate: %v", err) | ||||
| 		} else { | ||||
| 			r.TillerlessTemplate = nil | ||||
| 			r.Tillerless = tillerless | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	if r.VerifyTemplate != nil { | ||||
| 		if verify, err := getBoolRefFromStringTemplate(*r.VerifyTemplate); err != nil { | ||||
| 			return fmt.Errorf("verifyTemplate: %v", err) | ||||
| 		} else { | ||||
| 			r.VerifyTemplate = nil | ||||
| 			r.Verify = verify | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (st *HelmState) ExecuteTemplates() (*HelmState, error) { | ||||
| 	r := *st | ||||
| 
 | ||||
|  | @ -50,17 +102,32 @@ func (st *HelmState) ExecuteTemplates() (*HelmState, error) { | |||
| 	} | ||||
| 
 | ||||
| 	for i, rt := range st.Releases { | ||||
| 		tmplData := releaseTemplateData{ | ||||
| 			Environment: st.Env, | ||||
| 			Release:     rt, | ||||
| 			Values:      vals, | ||||
| 		successFlag := false | ||||
| 		for it, prev := 0, &rt; it < 6; it++ { | ||||
| 			tmplData := releaseTemplateData{ | ||||
| 				Environment: st.Env, | ||||
| 				Release:     *prev, | ||||
| 				Values:      vals, | ||||
| 			} | ||||
| 			renderer := tmpl.NewFileRenderer(st.readFile, st.basePath, tmplData) | ||||
| 			r, err := rt.ExecuteTemplateExpressions(renderer) | ||||
| 			if err != nil { | ||||
| 				return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, rt.Name, err) | ||||
| 			} | ||||
| 			if reflect.DeepEqual(prev, r) { | ||||
| 				successFlag = true | ||||
| 				if err := updateBoolTemplatedValues(r); err != nil { | ||||
| 					return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, rt.Name, err) | ||||
| 				} | ||||
| 				st.Releases[i] = *r | ||||
| 				break | ||||
| 			} | ||||
| 			prev = r | ||||
| 		} | ||||
| 		renderer := tmpl.NewFileRenderer(st.readFile, st.basePath, tmplData) | ||||
| 		r, err := rt.ExecuteTemplateExpressions(renderer) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %v", st.FilePath, rt.Name, err) | ||||
| 		if !successFlag { | ||||
| 			return nil, fmt.Errorf("failed executing templates in release \"%s\".\"%s\": %s", st.FilePath, rt.Name, | ||||
| 				"recursive references can't be resolved") | ||||
| 		} | ||||
| 		st.Releases[i] = *r | ||||
| 	} | ||||
| 
 | ||||
| 	return &r, nil | ||||
|  |  | |||
|  | @ -1,11 +1,27 @@ | |||
| package state | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/roboll/helmfile/pkg/environment" | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/go-test/deep" | ||||
| 	"github.com/roboll/helmfile/pkg/environment" | ||||
| ) | ||||
| 
 | ||||
| func boolPtrToString(ptr *bool) string { | ||||
| 	if ptr == nil { | ||||
| 		return "<nil>" | ||||
| 	} | ||||
| 	return fmt.Sprintf("&%t", *ptr) | ||||
| } | ||||
| 
 | ||||
| func ptr(v interface{}) interface{} { | ||||
| 	r := v | ||||
| 	return reflect.ValueOf(r).Addr().Interface() | ||||
| } | ||||
| 
 | ||||
| func TestHelmState_executeTemplates(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		name  string | ||||
|  | @ -13,24 +29,103 @@ func TestHelmState_executeTemplates(t *testing.T) { | |||
| 		want  ReleaseSpec | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "Has template expressions in chart, values, and secrets", | ||||
| 			name: "Has template expressions in chart, values, secrets, version, labels", | ||||
| 			input: ReleaseSpec{ | ||||
| 				Chart:     "test-charts/{{ .Release.Name }}", | ||||
| 				Version:   "{{ .Release.Name }}-0.1", | ||||
| 				Verify:    nil, | ||||
| 				Name:      "test-app", | ||||
| 				Namespace: "test-namespace-{{ .Release.Name }}", | ||||
| 				Values:    []interface{}{"config/{{ .Environment.Name }}/{{ .Release.Name }}/values.yaml"}, | ||||
| 				Secrets:   []string{"config/{{ .Environment.Name }}/{{ .Release.Name }}/secrets.yaml"}, | ||||
| 				Labels:    map[string]string{"id": "{{ .Release.Name }}"}, | ||||
| 			}, | ||||
| 			want: ReleaseSpec{ | ||||
| 				Chart:     "test-charts/test-app", | ||||
| 				Version:   "test-app-0.1", | ||||
| 				Verify:    nil, | ||||
| 				Name:      "test-app", | ||||
| 				Namespace: "test-namespace-test-app", | ||||
| 				Values:    []interface{}{"config/test_env/test-app/values.yaml"}, | ||||
| 				Secrets:   []string{"config/test_env/test-app/secrets.yaml"}, | ||||
| 				Labels:    map[string]string{"id": "test-app"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Has template expressions in name with recursive refs", | ||||
| 			input: ReleaseSpec{ | ||||
| 				Chart:     "test-chart", | ||||
| 				Name:      "{{ .Release.Labels.id }}-{{ .Release.Namespace }}", | ||||
| 				Namespace: "dev", | ||||
| 				Labels:    map[string]string{"id": "{{ .Release.Chart }}"}, | ||||
| 			}, | ||||
| 			want: ReleaseSpec{ | ||||
| 				Chart:     "test-chart", | ||||
| 				Name:      "test-chart-dev", | ||||
| 				Namespace: "dev", | ||||
| 				Labels:    map[string]string{"id": "test-chart"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Has template expressions in boolean values", | ||||
| 			input: ReleaseSpec{ | ||||
| 				Chart:              "test-chart", | ||||
| 				Name:               "app-dev", | ||||
| 				Namespace:          "dev", | ||||
| 				Labels:             map[string]string{"id": "app"}, | ||||
| 				InstalledTemplate:  func(i string) *string { return &i }(`{{ eq .Release.Labels.id "app" | ternary "yes" "no" }}`), | ||||
| 				VerifyTemplate:     func(i string) *string { return &i }(`{{ true }}`), | ||||
| 				Verify:             func(i bool) *bool { return &i }(false), | ||||
| 				WaitTemplate:       func(i string) *string { return &i }(`{{ false }}`), | ||||
| 				TillerlessTemplate: func(i string) *string { return &i }(`yes`), | ||||
| 			}, | ||||
| 			want: ReleaseSpec{ | ||||
| 				Chart:      "test-chart", | ||||
| 				Name:       "app-dev", | ||||
| 				Namespace:  "dev", | ||||
| 				Labels:     map[string]string{"id": "app"}, | ||||
| 				Installed:  func(i bool) *bool { return &i }(true), | ||||
| 				Verify:     func(i bool) *bool { return &i }(true), | ||||
| 				Wait:       func(i bool) *bool { return &i }(false), | ||||
| 				Tillerless: func(i bool) *bool { return &i }(true), | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Has template in set-values", | ||||
| 			input: ReleaseSpec{ | ||||
| 				Chart:     "test-charts/chart", | ||||
| 				Name:      "test-app", | ||||
| 				Namespace: "dev", | ||||
| 				SetValues: []SetValue{ | ||||
| 					SetValue{Name: "val1", Value: "{{ .Release.Name }}-val1"}, | ||||
| 					SetValue{Name: "val2", File: "{{ .Release.Name }}.yml"}, | ||||
| 					SetValue{Name: "val3", Values: []string{"{{ .Release.Name }}-val2", "{{ .Release.Name }}-val3"}}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			want: ReleaseSpec{ | ||||
| 				Chart:     "test-charts/chart", | ||||
| 				Name:      "test-app", | ||||
| 				Namespace: "dev", | ||||
| 				SetValues: []SetValue{ | ||||
| 					SetValue{Name: "val1", Value: "test-app-val1"}, | ||||
| 					SetValue{Name: "val2", File: "test-app.yml"}, | ||||
| 					SetValue{Name: "val3", Values: []string{"test-app-val2", "test-app-val3"}}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Has template in values (map)", | ||||
| 			input: ReleaseSpec{ | ||||
| 				Chart:     "test-charts/chart", | ||||
| 				Verify:    nil, | ||||
| 				Name:      "app", | ||||
| 				Namespace: "dev", | ||||
| 				Values:    []interface{}{map[string]string{"key": "{{ .Release.Name }}-val0"}}, | ||||
| 			}, | ||||
| 			want: ReleaseSpec{ | ||||
| 				Chart:     "test-charts/chart", | ||||
| 				Verify:    nil, | ||||
| 				Name:      "app", | ||||
| 				Namespace: "dev", | ||||
| 				Values:    []interface{}{map[interface{}]interface{}{"key": "app-val0"}}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | @ -59,20 +154,102 @@ func TestHelmState_executeTemplates(t *testing.T) { | |||
| 
 | ||||
| 			actual := r.Releases[0] | ||||
| 
 | ||||
| 			if !reflect.DeepEqual(actual.Name, tt.want.Name) { | ||||
| 				t.Errorf("expected Name %+v, got %+v", tt.want.Name, actual.Name) | ||||
| 			} | ||||
| 			if !reflect.DeepEqual(actual.Chart, tt.want.Chart) { | ||||
| 				t.Errorf("expected %+v, got %+v", tt.want.Chart, actual.Chart) | ||||
| 				t.Errorf("expected Chart %+v, got %+v", tt.want.Chart, actual.Chart) | ||||
| 			} | ||||
| 			if !reflect.DeepEqual(actual.Namespace, tt.want.Namespace) { | ||||
| 				t.Errorf("expected %+v, got %+v", tt.want.Namespace, actual.Namespace) | ||||
| 				t.Errorf("expected Namespace %+v, got %+v", tt.want.Namespace, actual.Namespace) | ||||
| 			} | ||||
| 			if !reflect.DeepEqual(actual.Values, tt.want.Values) { | ||||
| 				t.Errorf("expected %+v, got %+v", tt.want.Values, actual.Values) | ||||
| 			if diff := deep.Equal(actual.Values, tt.want.Values); diff != nil && len(actual.Values) > 0 { | ||||
| 				t.Errorf("Values differs \n%+v", strings.Join(diff, "\n")) | ||||
| 			} | ||||
| 			if !reflect.DeepEqual(actual.Secrets, tt.want.Secrets) { | ||||
| 				t.Errorf("expected %+v, got %+v", tt.want.Secrets, actual.Secrets) | ||||
| 			if diff := deep.Equal(actual.Secrets, tt.want.Secrets); diff != nil && len(actual.Secrets) > 0 { | ||||
| 				t.Errorf("Secrets differs \n%+v", strings.Join(diff, "\n")) | ||||
| 			} | ||||
| 			if diff := deep.Equal(actual.SetValues, tt.want.SetValues); diff != nil && len(actual.SetValues) > 0 { | ||||
| 				t.Errorf("SetValues differs \n%+v", strings.Join(diff, "\n")) | ||||
| 			} | ||||
| 			if diff := deep.Equal(actual.Labels, tt.want.Labels); diff != nil && len(actual.Labels) > 0 { | ||||
| 				t.Errorf("Labels differs \n%+v", strings.Join(diff, "\n")) | ||||
| 			} | ||||
| 			if !reflect.DeepEqual(actual.Version, tt.want.Version) { | ||||
| 				t.Errorf("expected %+v, got %+v", tt.want.Version, actual.Version) | ||||
| 				t.Errorf("expected Version %+v, got %+v", tt.want.Version, actual.Version) | ||||
| 			} | ||||
| 			if !reflect.DeepEqual(actual.Installed, tt.want.Installed) { | ||||
| 				t.Errorf("expected actual.Installed %+v, got %+v", | ||||
| 					boolPtrToString(tt.want.Installed), boolPtrToString(actual.Installed), | ||||
| 				) | ||||
| 			} | ||||
| 			if !reflect.DeepEqual(actual.Tillerless, tt.want.Tillerless) { | ||||
| 				t.Errorf("expected actual.Tillerless %+v, got %+v", | ||||
| 					boolPtrToString(tt.want.Tillerless), boolPtrToString(actual.Tillerless), | ||||
| 				) | ||||
| 			} | ||||
| 			if !reflect.DeepEqual(actual.Verify, tt.want.Verify) { | ||||
| 				t.Errorf("expected actual.Verify %+v, got %+v", | ||||
| 					boolPtrToString(tt.want.Verify), boolPtrToString(actual.Verify), | ||||
| 				) | ||||
| 			} | ||||
| 			if !reflect.DeepEqual(actual.Wait, tt.want.Wait) { | ||||
| 				t.Errorf("expected actual.Wait %+v, got %+v", | ||||
| 					boolPtrToString(tt.want.Wait), boolPtrToString(actual.Wait), | ||||
| 				) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func TestHelmState_recursiveRefsTemplates(t *testing.T) { | ||||
| 
 | ||||
| 	tests := []struct { | ||||
| 		name  string | ||||
| 		input ReleaseSpec | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name: "Has reqursive references", | ||||
| 			input: ReleaseSpec{ | ||||
| 				Chart:     "test-charts/{{ .Release.Name }}", | ||||
| 				Verify:    nil, | ||||
| 				Name:      "{{ .Release.Labels.id }}", | ||||
| 				Namespace: "dev", | ||||
| 				Labels:    map[string]string{"id": "app-{{ .Release.Name }}"}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "Has unresolvable boolean templates", | ||||
| 			input: ReleaseSpec{ | ||||
| 				Name:         "app-dev", | ||||
| 				Chart:        "test-charts/app", | ||||
| 				Verify:       nil, | ||||
| 				Namespace:    "dev", | ||||
| 				WaitTemplate: func(i string) *string { return &i }("hi"), | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	for i := range tests { | ||||
| 		tt := tests[i] | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			state := &HelmState{ | ||||
| 				basePath: ".", | ||||
| 				HelmDefaults: HelmSpec{ | ||||
| 					KubeContext: "test_context", | ||||
| 				}, | ||||
| 				Env:          environment.Environment{Name: "test_env"}, | ||||
| 				Namespace:    "test-namespace_", | ||||
| 				Repositories: nil, | ||||
| 				Releases: []ReleaseSpec{ | ||||
| 					tt.input, | ||||
| 				}, | ||||
| 			} | ||||
| 
 | ||||
| 			r, err := state.ExecuteTemplates() | ||||
| 			if err == nil { | ||||
| 				t.Errorf("Expected error, got valid response: %v", r) | ||||
| 				t.FailNow() | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
|  |  | |||
|  | @ -706,7 +706,7 @@ func (helm *mockHelmExec) UpdateDeps(chart string) error { | |||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (helm *mockHelmExec) BuildDeps(chart string) error { | ||||
| func (helm *mockHelmExec) BuildDeps(name, chart string) error { | ||||
| 	if strings.Contains(chart, "error") { | ||||
| 		return errors.New("error") | ||||
| 	} | ||||
|  | @ -769,10 +769,10 @@ func (helm *mockHelmExec) TestRelease(context helmexec.HelmContext, name string, | |||
| func (helm *mockHelmExec) Fetch(chart string, flags ...string) error { | ||||
| 	return nil | ||||
| } | ||||
| func (helm *mockHelmExec) Lint(chart string, flags ...string) error { | ||||
| func (helm *mockHelmExec) Lint(name, chart string, flags ...string) error { | ||||
| 	return nil | ||||
| } | ||||
| func (helm *mockHelmExec) TemplateRelease(chart string, flags ...string) error { | ||||
| func (helm *mockHelmExec) TemplateRelease(name, chart string, flags ...string) error { | ||||
| 	return nil | ||||
| } | ||||
| func TestHelmState_SyncRepos(t *testing.T) { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue