feat: Add `needs: [NS/NAME]` for controlling installation/deletion order declaratively (#914)
Introduces DAG-aware installation/deletion ordering to Helmfile.
`needs` controls the order of the installation/deletion of the release:
```yaml
relesaes:
- name: somerelease
needs:
- [TILLER_NAMESPACE/][NAMESPACE/]anotherelease
```
All the releases listed under `needs` are installed before(or deleted after) the release itself.
For the following example, `helmfile [sync|apply]` installs releases in this order:
1. logging
2. servicemesh
3. myapp1 and myapp2
```yaml
- name: myapp1
chart: charts/myapp
needs:
- servicemesh
- logging
- name: myapp2
chart: charts/myapp
needs:
- servicemesh
- logging
- name: servicemesh
chart: charts/istio
needs:
- logging
- name: logging
chart: charts/fluentd
```
Note that all the releases in a same group is installed concurrently. That is, myapp1 and myapp2 are installed concurrently.
On `helmdile [delete|destroy]`, deleations happen in the reverse order.
That is, `myapp1` and `myapp2` are deleted first, then `servicemesh`, and finally `logging`.
Resolves #715
This commit is contained in:
parent
b8f24948bb
commit
7666e95690
44
README.md
44
README.md
|
|
@ -723,6 +723,50 @@ With the [helm-tiller](https://github.com/rimusz/helm-tiller) plugin installed,
|
||||||
To enable this mode, you need to define `tillerless: true` and set the `tillerNamespace` in the `helmDefaults` section
|
To enable this mode, you need to define `tillerless: true` and set the `tillerNamespace` in the `helmDefaults` section
|
||||||
or in the `releases` entries.
|
or in the `releases` entries.
|
||||||
|
|
||||||
|
## DAG-aware installation/deletion ordering
|
||||||
|
|
||||||
|
`needs` controls the order of the installation/deletion of the release:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
relesaes:
|
||||||
|
- name: somerelease
|
||||||
|
needs:
|
||||||
|
- [TILLER_NAMESPACE/][NAMESPACE/]anotherelease
|
||||||
|
```
|
||||||
|
|
||||||
|
All the releases listed under `needs` are installed before(or deleted after) the release itself.
|
||||||
|
|
||||||
|
For the following example, `helmfile [sync|apply]` installs releases in this order:
|
||||||
|
|
||||||
|
1. logging
|
||||||
|
2. servicemesh
|
||||||
|
3. myapp1 and myapp2
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: myapp1
|
||||||
|
chart: charts/myapp
|
||||||
|
needs:
|
||||||
|
- servicemesh
|
||||||
|
- logging
|
||||||
|
- name: myapp2
|
||||||
|
chart: charts/myapp
|
||||||
|
needs:
|
||||||
|
- servicemesh
|
||||||
|
- logging
|
||||||
|
- name: servicemesh
|
||||||
|
chart: charts/istio
|
||||||
|
needs:
|
||||||
|
- logging
|
||||||
|
- name: logging
|
||||||
|
chart: charts/fluentd
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that all the releases in a same group is installed concurrently. That is, myapp1 and myapp2 are installed concurrently.
|
||||||
|
|
||||||
|
On `helmdile [delete|destroy]`, deleations happen in the reverse order.
|
||||||
|
|
||||||
|
That is, `myapp1` and `myapp2` are deleted first, then `servicemesh`, and finally `logging`.
|
||||||
|
|
||||||
## Separating helmfile.yaml into multiple independent files
|
## Separating helmfile.yaml into multiple independent files
|
||||||
|
|
||||||
Once your `helmfile.yaml` got to contain too many releases,
|
Once your `helmfile.yaml` got to contain too many releases,
|
||||||
|
|
|
||||||
1
go.mod
1
go.mod
|
|
@ -25,6 +25,7 @@ require (
|
||||||
github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad
|
github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad
|
||||||
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
|
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
|
||||||
github.com/urfave/cli v1.20.0
|
github.com/urfave/cli v1.20.0
|
||||||
|
github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363
|
||||||
github.com/variantdev/vals v0.0.0-20191026125821-5d18b16cf30a
|
github.com/variantdev/vals v0.0.0-20191026125821-5d18b16cf30a
|
||||||
go.mozilla.org/sops v0.0.0-20190912205235-14a22d7a7060 // indirect
|
go.mozilla.org/sops v0.0.0-20190912205235-14a22d7a7060 // indirect
|
||||||
go.opencensus.io v0.22.1 // indirect
|
go.opencensus.io v0.22.1 // indirect
|
||||||
|
|
|
||||||
9
go.sum
9
go.sum
|
|
@ -18,6 +18,7 @@ cloud.google.com/go v0.47.0/go.mod h1:5p3Ky/7f3N10VBkhuR5LFtddroTiMyjZV/Kj5qOQFx
|
||||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||||
|
cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4=
|
||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI=
|
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI=
|
||||||
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
|
contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA=
|
||||||
|
|
@ -85,6 +86,7 @@ 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/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 h1:CaDA1wAoM3rj9sAFyyZP37LloExUzxFGYt+DqJ870JA=
|
||||||
github.com/Masterminds/semver v1.4.1/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
github.com/Masterminds/semver v1.4.1/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
|
github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc=
|
||||||
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||||
|
|
@ -228,6 +230,7 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
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-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
|
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/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
|
|
@ -240,6 +243,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
||||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||||
github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c h1:jWtZjFEUE/Bz0IeIhqCnyZ3HG6KRXSntXe4SjtuTH7c=
|
github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c h1:jWtZjFEUE/Bz0IeIhqCnyZ3HG6KRXSntXe4SjtuTH7c=
|
||||||
github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v0.0.0-20161128191214-064e2069ce9c/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
|
||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
github.com/googleapis/gax-go v2.0.0+incompatible h1:j0GKcs05QVmm7yesiZq2+9cxHkNK9YM6zKx4D2qucQU=
|
||||||
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
|
||||||
|
|
@ -313,6 +317,7 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
|
||||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
|
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
|
||||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
|
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
||||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
|
|
@ -577,7 +582,10 @@ github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok=
|
||||||
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||||
github.com/urfave/cli v0.0.0-20160620154522-6011f165dc28 h1:Yf7/DcGM+61oLBGXQV2Q+ztEGBcOe3EUnbKSOn4fQuE=
|
github.com/urfave/cli v0.0.0-20160620154522-6011f165dc28 h1:Yf7/DcGM+61oLBGXQV2Q+ztEGBcOe3EUnbKSOn4fQuE=
|
||||||
github.com/urfave/cli v0.0.0-20160620154522-6011f165dc28/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
github.com/urfave/cli v0.0.0-20160620154522-6011f165dc28/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
|
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
|
github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363 h1:KrfQBEUn+wEOQ/6UIfoqRDvn+Q/wZridQ7t0G1vQqKE=
|
||||||
|
github.com/variantdev/dag v0.0.0-20191028002400-bb0b3c785363/go.mod h1:pH1TQsNSLj2uxMo9NNl9zdGy01Wtn+/2MT96BrKmVyE=
|
||||||
github.com/variantdev/vals v0.0.0-20191025124021-e86de6f8cd7d h1:od9EUts72ELxOSrqJxtbxAU/c30XCaA+xnq0jDWMQrA=
|
github.com/variantdev/vals v0.0.0-20191025124021-e86de6f8cd7d h1:od9EUts72ELxOSrqJxtbxAU/c30XCaA+xnq0jDWMQrA=
|
||||||
github.com/variantdev/vals v0.0.0-20191025124021-e86de6f8cd7d/go.mod h1:7Gb8avEj7D9rMIqn4Nn9RBKXo/0rZ7Z0e+3bmHIs420=
|
github.com/variantdev/vals v0.0.0-20191025124021-e86de6f8cd7d/go.mod h1:7Gb8avEj7D9rMIqn4Nn9RBKXo/0rZ7Z0e+3bmHIs420=
|
||||||
github.com/variantdev/vals v0.0.0-20191026125821-5d18b16cf30a h1:zrV+XXPXniLy9ZVHUN7DLVVKnMjActASxwsMKIohfLA=
|
github.com/variantdev/vals v0.0.0-20191026125821-5d18b16cf30a h1:zrV+XXPXniLy9ZVHUN7DLVVKnMjActASxwsMKIohfLA=
|
||||||
|
|
@ -608,6 +616,7 @@ go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/zap v1.8.0 h1:r6Za1Rii8+EGOYRDLvpooNOF6kP3iyDnkpzbw67gCQ8=
|
go.uber.org/zap v1.8.0 h1:r6Za1Rii8+EGOYRDLvpooNOF6kP3iyDnkpzbw67gCQ8=
|
||||||
go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
|
||||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
|
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/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/roboll/helmfile/pkg/helmexec"
|
"github.com/roboll/helmfile/pkg/helmexec"
|
||||||
"github.com/roboll/helmfile/pkg/remote"
|
"github.com/roboll/helmfile/pkg/remote"
|
||||||
"github.com/roboll/helmfile/pkg/tmpl"
|
"github.com/roboll/helmfile/pkg/tmpl"
|
||||||
|
"github.com/variantdev/dag/pkg/dag"
|
||||||
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
|
@ -144,6 +145,8 @@ type ReleaseSpec struct {
|
||||||
// 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.
|
// 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".
|
// The default value for MissingFileHandler is "Error".
|
||||||
MissingFileHandler *string `yaml:"missingFileHandler,omitempty"`
|
MissingFileHandler *string `yaml:"missingFileHandler,omitempty"`
|
||||||
|
// Needs is the [TILLER_NS/][NS/]NAME representations of releases that this release depends on.
|
||||||
|
Needs []string `yaml:"needs,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 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,omitempty"`
|
Hooks []event.Hook `yaml:"hooks,omitempty"`
|
||||||
|
|
@ -408,12 +411,89 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
|
||||||
return prepErrs
|
return prepErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
availableIds := make([]string, len(preps))
|
||||||
|
idToPrep := map[string]syncPrepareResult{}
|
||||||
|
|
||||||
|
d := dag.New()
|
||||||
|
for i, p := range preps {
|
||||||
|
r := p.release
|
||||||
|
|
||||||
|
id := releaseToID(r)
|
||||||
|
|
||||||
|
idToPrep[id] = p
|
||||||
|
|
||||||
|
availableIds[i] = id
|
||||||
|
|
||||||
|
d.Add(id, dag.Dependencies(r.Needs))
|
||||||
|
}
|
||||||
|
|
||||||
|
plan, err := d.Plan()
|
||||||
|
if err != nil {
|
||||||
|
return []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsTotal := len(plan)
|
||||||
|
|
||||||
|
st.logger.Debugf("syncing %d groups of releases in this order: %s", groupsTotal, plan)
|
||||||
|
|
||||||
|
for id, prep := range idToPrep {
|
||||||
|
for _, need := range prep.release.Needs {
|
||||||
|
if _, ok := idToPrep[need]; !ok {
|
||||||
|
return []error{fmt.Errorf("%q needs %q, but it must be one of %s", id, need, strings.Join(availableIds, ", "))}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for groupIndex, dagNodesInGroup := range plan {
|
||||||
|
var idsInGroup []string
|
||||||
|
var prepsInGroup []syncPrepareResult
|
||||||
|
|
||||||
|
for _, node := range dagNodesInGroup {
|
||||||
|
prepareResult, ok := idToPrep[node.Id]
|
||||||
|
if !ok {
|
||||||
|
panic(fmt.Sprintf("[bug] no release found for dag node id %q", node.Id))
|
||||||
|
}
|
||||||
|
prepsInGroup = append(prepsInGroup, prepareResult)
|
||||||
|
idsInGroup = append(idsInGroup, node.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
st.logger.Debugf("syncing releases in group %d/%d: %s", groupIndex+1, groupsTotal, strings.Join(idsInGroup, ", "))
|
||||||
|
|
||||||
|
errs := st.syncReleaseGroup(affectedReleases, helm, prepsInGroup)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseToID(r *ReleaseSpec) string {
|
||||||
|
var id string
|
||||||
|
|
||||||
|
tns := r.TillerNamespace
|
||||||
|
if tns != "" {
|
||||||
|
id += tns + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
ns := r.Namespace
|
||||||
|
if ns != "" {
|
||||||
|
id += ns + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
id += r.Name
|
||||||
|
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *HelmState) syncReleaseGroup(affectedReleases *AffectedReleases, helm helmexec.Interface, preps []syncPrepareResult) []error {
|
||||||
errs := []error{}
|
errs := []error{}
|
||||||
jobQueue := make(chan *syncPrepareResult, len(preps))
|
jobQueue := make(chan *syncPrepareResult, len(preps))
|
||||||
results := make(chan syncResult, len(preps))
|
results := make(chan syncResult, len(preps))
|
||||||
|
concurrency := len(preps)
|
||||||
|
|
||||||
st.scatterGather(
|
st.scatterGather(
|
||||||
workerLimit,
|
concurrency,
|
||||||
len(preps),
|
len(preps),
|
||||||
func() {
|
func() {
|
||||||
for i := 0; i < len(preps); i++ {
|
for i := 0; i < len(preps); i++ {
|
||||||
|
|
@ -1010,8 +1090,9 @@ func (st *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int) [
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteReleases wrapper for executing helm delete on the releases
|
// DeleteReleases wrapper for executing helm delete on the releases
|
||||||
|
// This function traverses the DAG of the releases in the reverse order, so that the releases that are NOT depended by any others are deleted first.
|
||||||
func (st *HelmState) DeleteReleases(affectedReleases *AffectedReleases, helm helmexec.Interface, concurrency int, purge bool) []error {
|
func (st *HelmState) DeleteReleases(affectedReleases *AffectedReleases, helm helmexec.Interface, concurrency int, purge bool) []error {
|
||||||
return st.scatterGatherReleases(helm, concurrency, func(release ReleaseSpec, workerIndex int) error {
|
return st.dagAwareReverseIterateOnReleases(helm, concurrency, func(release ReleaseSpec, workerIndex int) error {
|
||||||
if !release.Desired() {
|
if !release.Desired() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/roboll/helmfile/pkg/helmexec"
|
"github.com/roboll/helmfile/pkg/helmexec"
|
||||||
|
"github.com/variantdev/dag/pkg/dag"
|
||||||
)
|
)
|
||||||
|
|
||||||
type result struct {
|
type result struct {
|
||||||
|
|
@ -51,9 +53,14 @@ func (st *HelmState) scatterGather(concurrency int, items int, produceInputs fun
|
||||||
|
|
||||||
func (st *HelmState) scatterGatherReleases(helm helmexec.Interface, concurrency int,
|
func (st *HelmState) scatterGatherReleases(helm helmexec.Interface, concurrency int,
|
||||||
do func(ReleaseSpec, int) error) []error {
|
do func(ReleaseSpec, int) error) []error {
|
||||||
|
|
||||||
|
return st.iterateOnReleases(helm, concurrency, st.Releases, do)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *HelmState) iterateOnReleases(helm helmexec.Interface, concurrency int, inputs []ReleaseSpec,
|
||||||
|
do func(ReleaseSpec, int) error) []error {
|
||||||
var errs []error
|
var errs []error
|
||||||
|
|
||||||
inputs := st.Releases
|
|
||||||
inputsSize := len(inputs)
|
inputsSize := len(inputs)
|
||||||
|
|
||||||
releases := make(chan ReleaseSpec)
|
releases := make(chan ReleaseSpec)
|
||||||
|
|
@ -96,3 +103,52 @@ func (st *HelmState) scatterGatherReleases(helm helmexec.Interface, concurrency
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (st *HelmState) dagAwareReverseIterateOnReleases(helm helmexec.Interface, concurrency int,
|
||||||
|
do func(ReleaseSpec, int) error) []error {
|
||||||
|
|
||||||
|
idToRelease := map[string]ReleaseSpec{}
|
||||||
|
|
||||||
|
preps := st.Releases
|
||||||
|
|
||||||
|
d := dag.New()
|
||||||
|
for _, r := range preps {
|
||||||
|
|
||||||
|
id := releaseToID(&r)
|
||||||
|
|
||||||
|
idToRelease[id] = r
|
||||||
|
|
||||||
|
d.Add(id, dag.Dependencies(r.Needs))
|
||||||
|
}
|
||||||
|
|
||||||
|
plan, err := d.Plan()
|
||||||
|
if err != nil {
|
||||||
|
return []error{err}
|
||||||
|
}
|
||||||
|
|
||||||
|
groupsTotal := len(plan)
|
||||||
|
|
||||||
|
st.logger.Debugf("processing %d groups of releases in this order: %s", groupsTotal, plan)
|
||||||
|
|
||||||
|
for groupIndex := len(plan) - 1; groupIndex >= 0; groupIndex-- {
|
||||||
|
dagNodesInGroup := plan[groupIndex]
|
||||||
|
|
||||||
|
var idsInGroup []string
|
||||||
|
var releasesInGroup []ReleaseSpec
|
||||||
|
|
||||||
|
for _, node := range dagNodesInGroup {
|
||||||
|
releasesInGroup = append(releasesInGroup, idToRelease[node.Id])
|
||||||
|
idsInGroup = append(idsInGroup, node.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
st.logger.Debugf("processing releases in group %d/%d: %s", groupIndex+1, groupsTotal, strings.Join(idsInGroup, ", "))
|
||||||
|
|
||||||
|
errs := st.iterateOnReleases(helm, concurrency, releasesInGroup, do)
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -867,10 +867,11 @@ func TestHelmState_SyncRepos(t *testing.T) {
|
||||||
|
|
||||||
func TestHelmState_SyncReleases(t *testing.T) {
|
func TestHelmState_SyncReleases(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
releases []ReleaseSpec
|
releases []ReleaseSpec
|
||||||
helm *mockHelmExec
|
helm *mockHelmExec
|
||||||
wantReleases []mockRelease
|
wantReleases []mockRelease
|
||||||
|
wantErrorMsgs []string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "normal release",
|
name: "normal release",
|
||||||
|
|
@ -961,6 +962,106 @@ func TestHelmState_SyncReleases(t *testing.T) {
|
||||||
helm: &mockHelmExec{},
|
helm: &mockHelmExec{},
|
||||||
wantReleases: []mockRelease{{"releaseName", []string{"--set", "foo.bar[0]={A,B}"}}},
|
wantReleases: []mockRelease{{"releaseName", []string{"--set", "foo.bar[0]={A,B}"}}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "foo needs bar",
|
||||||
|
releases: []ReleaseSpec{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Chart: "charts/foo",
|
||||||
|
Needs: []string{
|
||||||
|
"bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Chart: "charts/bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
helm: &mockHelmExec{},
|
||||||
|
wantReleases: []mockRelease{{"bar", []string{}}, {"foo", []string{}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bar needs foo",
|
||||||
|
releases: []ReleaseSpec{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Chart: "charts/foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Chart: "charts/bar",
|
||||||
|
Needs: []string{
|
||||||
|
"foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
helm: &mockHelmExec{},
|
||||||
|
wantReleases: []mockRelease{{"foo", []string{}}, {"bar", []string{}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ns2/bar needs ns1/foo",
|
||||||
|
releases: []ReleaseSpec{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "ns1",
|
||||||
|
Chart: "charts/foo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Namespace: "ns2",
|
||||||
|
Chart: "charts/bar",
|
||||||
|
Needs: []string{
|
||||||
|
"ns1/foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
helm: &mockHelmExec{},
|
||||||
|
wantReleases: []mockRelease{{"foo", []string{"--namespace", "ns1"}}, {"bar", []string{"--namespace", "ns2"}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tillerns1/ns1/foo needs tillerns2/ns2/bar",
|
||||||
|
releases: []ReleaseSpec{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Chart: "charts/foo",
|
||||||
|
Namespace: "ns1",
|
||||||
|
TillerNamespace: "tillerns1",
|
||||||
|
Needs: []string{
|
||||||
|
"tillerns2/ns2/bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Namespace: "ns2",
|
||||||
|
TillerNamespace: "tillerns2",
|
||||||
|
Chart: "charts/bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
helm: &mockHelmExec{},
|
||||||
|
wantReleases: []mockRelease{{"bar", []string{"--tiller-namespace", "tillerns2", "--namespace", "ns2"}}, {"foo", []string{"--tiller-namespace", "tillerns1", "--namespace", "ns1"}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tillerns1/ns1/foo needs tillerns2/ns2/bar",
|
||||||
|
releases: []ReleaseSpec{
|
||||||
|
{
|
||||||
|
Name: "foo",
|
||||||
|
Chart: "charts/foo",
|
||||||
|
Namespace: "ns1",
|
||||||
|
TillerNamespace: "tillerns1",
|
||||||
|
Needs: []string{
|
||||||
|
"bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "bar",
|
||||||
|
Namespace: "ns2",
|
||||||
|
TillerNamespace: "tillerns2",
|
||||||
|
Chart: "charts/bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
helm: &mockHelmExec{},
|
||||||
|
wantErrorMsgs: []string{`"tillerns1/ns1/foo" needs "bar", but it must be one of tillerns1/ns1/foo, tillerns2/ns2/bar`},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for i := range tests {
|
for i := range tests {
|
||||||
tt := tests[i]
|
tt := tests[i]
|
||||||
|
|
@ -970,7 +1071,23 @@ func TestHelmState_SyncReleases(t *testing.T) {
|
||||||
logger: logger,
|
logger: logger,
|
||||||
valsRuntime: valsRuntime,
|
valsRuntime: valsRuntime,
|
||||||
}
|
}
|
||||||
if _ = state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); !reflect.DeepEqual(tt.helm.releases, tt.wantReleases) {
|
if errs := state.SyncReleases(&AffectedReleases{}, tt.helm, []string{}, 1); errs != nil && len(errs) > 0 {
|
||||||
|
if len(errs) != len(tt.wantErrorMsgs) {
|
||||||
|
t.Fatalf("Unexpected errors: %v\nExpected: %v", errs, tt.wantErrorMsgs)
|
||||||
|
}
|
||||||
|
var mismatch int
|
||||||
|
for i := range tt.wantErrorMsgs {
|
||||||
|
expected := tt.wantErrorMsgs[i]
|
||||||
|
actual := errs[i].Error()
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Errorf("Unexpected error: expected=%v, got=%v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mismatch > 0 {
|
||||||
|
t.Fatalf("%d unexpected errors detected", mismatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(tt.helm.releases, tt.wantReleases) {
|
||||||
t.Errorf("HelmState.SyncReleases() for [%s] = %v, want %v", tt.name, tt.helm.releases, tt.wantReleases)
|
t.Errorf("HelmState.SyncReleases() for [%s] = %v, want %v", tt.name, tt.helm.releases, tt.wantReleases)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue