From 3f02b866401ba7ef34c4c985de13151f3e51fb7b Mon Sep 17 00:00:00 2001 From: KUOKA Yusuke Date: Sat, 2 Nov 2019 14:04:16 +0900 Subject: [PATCH] fix: Fix `needs` to work for upgrades and when selectors are provided (#922) * fix: Fix `needs` to work for upgrades and when selectors are provided Fixes #919 * Add test framework for `helmfile apply` * Various enhancements and fixes to the DAG support - Make the order of upgrades/deletes more deterministic for testability - Fix the test framework so that we can validate log outputs and errors - Add more test cases for `helmfile apply`, along with bug fixes. - Make sure it fails with an intuitive error when you have non-existent releases referenced from witin "needs" --- go.mod | 1 + go.sum | 140 +++ pkg/app/app.go | 257 +++++- pkg/app/app_test.go | 1235 +++++++++++++++++++++++++- pkg/app/desired_state_file_loader.go | 4 +- pkg/app/run.go | 98 -- pkg/app/two_pass_renderer.go | 5 +- pkg/exectest/helm.go | 167 ++++ pkg/helmexec/exit_error.go | 12 +- pkg/state/state.go | 274 +++--- pkg/state/state_exec_tmpl.go | 2 +- pkg/state/state_exec_tmpl_test.go | 12 +- pkg/state/state_run.go | 89 +- pkg/state/state_test.go | 497 ++++------- pkg/testhelper/diff.go | 103 +++ 15 files changed, 2278 insertions(+), 618 deletions(-) create mode 100644 pkg/exectest/helm.go create mode 100644 pkg/testhelper/diff.go diff --git a/go.mod b/go.mod index 516e487f..340efe76 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/Masterminds/goutils v1.1.0 // indirect github.com/Masterminds/semver v1.4.2 github.com/Masterminds/sprig v2.22.0+incompatible + github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a github.com/aws/aws-sdk-go v1.25.22 // indirect github.com/go-test/deep v1.0.3 github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect diff --git a/go.sum b/go.sum index f3701d38..ad204812 100644 --- a/go.sum +++ b/go.sum @@ -15,11 +15,15 @@ cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTj cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.47.0 h1:1JUtpcY9E7+eTospEwWS2QXP3DEn7poB3E2j0jN74mM= cloud.google.com/go v0.47.0/go.mod h1:5p3Ky/7f3N10VBkhuR5LFtddroTiMyjZV/Kj5qOQFxU= +cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8= 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= +code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk= 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= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= @@ -37,6 +41,7 @@ github.com/Azure/azure-sdk-for-go v34.4.0+incompatible h1:NQG6PyG1+y9lL5SLWy+urw github.com/Azure/azure-sdk-for-go v34.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v35.0.0+incompatible h1:PkmdmQUmeSdQQ5258f4SyCf2Zcz0w67qztEg37cOR7U= github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-autorest v11.7.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest v13.2.0+incompatible h1:jK9tnWNPzdvf5gTuueYWLCfIvAhg15iZ2f6+qkD43s8= @@ -68,6 +73,7 @@ github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSW github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= github.com/Azure/go-autorest/autorest/to v0.3.0 h1:zebkZaadz7+wIQYgC7GXaz3Wb28yKYfVkkBKwc38VF8= @@ -84,6 +90,7 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/Jeffail/gabs v1.1.1 h1:V0uzR08Hj22EX8+8QMhyI9sX2hwRu+/RJhJUmnwda/E= github.com/Jeffail/gabs v1.1.1/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= @@ -93,9 +100,13 @@ github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITg 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/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.4.13 h1:Hmi80lzZuI/CaYmlJp/b+FjZdRZhKu9c2mDVqKlLWVs= github.com/Microsoft/go-winio v0.4.13/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/SAP/go-hdb v0.14.1 h1:hkw4ozGZ/i4eak7ZuGkY5e0hxiXFdNUBNhr4AvZVNFE= github.com/SAP/go-hdb v0.14.1/go.mod h1:7fdQLVC2lER3urZLjZCm0AuMQfApof92n3aylBPEkMo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= @@ -103,6 +114,7 @@ github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJM github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190412020505-60e2075261b6/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= +github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f h1:oRD16bhpKNAanfcDDVU+J0NXqsgHIvGbbe/sy+r6Rs0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190620160927-9418d7b0cd0f/go.mod h1:myCDvQSzCW+wB1WAlocEru4wMGJxy+vlxHdhegi1CDQ= github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190307165228-86c17b95fcd5/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= @@ -110,10 +122,15 @@ github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb github.com/apple/foundationdb/bindings/go v0.0.0-20190411004307-cd5c9d91fad2/go.mod h1:OMVSB21p9+xQUIqlGizHPZfjK+SHws1ht+ZytVDoz9U= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878 h1:EFSB7Zo9Eg91v7MJPVsifUysc/wPdN+NOnVe6bWbdBM= github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= github.com/armon/go-proxyproto v0.0.0-20190211145416-68259f75880e/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a h1:pv34s756C4pEXnjgPfGYgdhg/ZdajGhyOvzx8k+23nw= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 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= @@ -126,30 +143,42 @@ github.com/aws/aws-sdk-go v1.25.19/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpi github.com/aws/aws-sdk-go v1.25.22 h1:DXcA0jjMnGt2awoWM2qwCu+ouGDB5FYnGxCVrRweE/0= github.com/aws/aws-sdk-go v1.25.22/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= +github.com/boombuler/barcode v1.0.0 h1:s1TvRnXwL2xJRaccrdcBQMZxq6X7DvsMogtmJeHDdrc= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/briankassouf/jose v0.9.2-0.20180619214549-d2569464773f h1:ZMEzE7R0WNqgbHplzSBaYJhJi5AZWTCK9baU0ebzG6g= github.com/briankassouf/jose v0.9.2-0.20180619214549-d2569464773f/go.mod h1:HQhVmdUf7dBNwIIdBTivnCDxcf6IZY3/zrb+uKSJz6Y= +github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/centrify/cloud-golang-sdk v0.0.0-20190214225812-119110094d0f h1:gJzxrodnNd/CtPXjO3WYiakyNzHg3rtAi7rO74ejHYU= github.com/centrify/cloud-golang-sdk v0.0.0-20190214225812-119110094d0f/go.mod h1:C0rtzmGXgN78pYR0tGJFhtHgkbAs0lIbHwkB81VxDQE= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/chrismalek/oktasdk-go v0.0.0-20181212195951-3430665dfaa0 h1:CWU8piLyqoi9qXEUwzOh5KFKGgmSU5ZhktJyYcq6ryQ= github.com/chrismalek/oktasdk-go v0.0.0-20181212195951-3430665dfaa0/go.mod h1:5d8DqS60xkj9k3aXfL3+mXBH0DPYO0FQjcKosxl+b/Q= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 h1:rdRS5BT13Iae9ssvcslol66gfOOXjaLYwqerEn/cl9s= github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381/go.mod h1:e5+USP2j8Le2M0Jo3qKPFnNhuo1wueU4nWHCXBOfQ14= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/coreos/go-oidc v2.0.0+incompatible h1:+RStIopZ8wooMx+Vs5Bt8zMXxV1ABl5LbakNExNmZIg= github.com/coreos/go-oidc v2.0.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -158,43 +187,58 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20190412130859-3b1d194e553a h1:yJ2kD1BvM28M4gt31MuDr0ROKsW+v6zBk9G0Bcr8qAY= github.com/denisenkom/go-mssqldb v0.0.0-20190412130859-3b1d194e553a/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4= github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74 h1:2MIhn2R6oXQbgW5yHfS+d6YqyMfXiu2L55rFZC4UD/M= github.com/duosecurity/duo_api_golang v0.0.0-20190308151101-6c680f768e74/go.mod h1:UqXY1lYT/ERa4OEAywUqdok1T4RCRdArkhic1Opuavo= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/frankban/quicktest v1.4.1 h1:Wv2VwvNn73pAdFIVUQRXYDFp31lXKbqblIXo/Q5GPSg= github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/gammazero/deque v0.0.0-20190130191400-2afb3858e9c7 h1:D2LrfOPgGHQprIxmsTpxtzhpmF66HoM6rXSmcqaX7h8= github.com/gammazero/deque v0.0.0-20190130191400-2afb3858e9c7/go.mod h1:GeIq9qoE43YdGnDXURnmKTnGg15pQz4mYkXSTChbneI= +github.com/gammazero/workerpool v0.0.0-20190406235159-88d534f22b56 h1:VzbudKn/nvxYKOdzgkEBS6SSreRjAgoJ+ZeS4wPFkgc= github.com/gammazero/workerpool v0.0.0-20190406235159-88d534f22b56/go.mod h1:w9RqFVO2BM3xwWEcAB8Fwp0OviTBBEiRmSBDfbXnd3w= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-ldap/ldap v3.0.2+incompatible h1:kD5HQcAzlQ7yrhfn+h+MSABeAy/jAJhvIJ/QDllP44g= github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iaueeTaUgdetzel+U7exyigDYBryyVfV/rZk= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= @@ -202,10 +246,12 @@ github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr6 github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= 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/gocql/gocql v0.0.0-20190402132108-0e1d5de854df h1:fwXmhM0OqixzJDOGgTSyNH9eEDij9uGTXwsyWXvyR0A= github.com/gocql/gocql v0.0.0-20190402132108-0e1d5de854df/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= @@ -233,14 +279,18 @@ github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 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-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -261,13 +311,16 @@ github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75 h1:f0n1xnMSmBLzVfsMMvriDyA75NB/oBgILX2GcHXIQzY= github.com/gorhill/cronexpr v0.0.0-20180427100037-88b0669f7d75/go.mod h1:g2644b03hfBX9Ov0ZBDgXXens4rxSxmqFBbhvKv2yVA= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gosuri/uitable v0.0.3 h1:9ZY4qCODg6JL1Ui4dL9LqCF4ghWnAOSV2h7xG98SkHE= github.com/gosuri/uitable v0.0.3/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= github.com/goware/prefixer v0.0.0-20160118172347-395022866408 h1:Y9iQJfEqnN3/Nce9cOegemcy/9Ai5k3huT6E80F3zaw= github.com/goware/prefixer v0.0.0-20160118172347-395022866408/go.mod h1:PE1ycukgRPJ7bJ9a1fdfQ9j8i/cEcRAoLZzbxYpNB/s= @@ -278,9 +331,12 @@ github.com/grpc-ecosystem/grpc-gateway v1.4.1/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpg github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/consul/api v1.0.1 h1:LkHu3cLXjya4lgrAyZVe/CUBXgJ7AcDWKSeCjAYN9w0= github.com/hashicorp/consul/api v1.0.1/go.mod h1:LQlewHPiuaRhn1mP2XE4RrjnlRgOeWa/ZM0xWLCen2M= github.com/hashicorp/consul/sdk v0.1.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -288,6 +344,7 @@ github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6K github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-gcp-common v0.5.0 h1:kkIQTjNTopn4eXQ1+lCiHYZXUtgIZvbc6YtAQkMnTos= github.com/hashicorp/go-gcp-common v0.5.0/go.mod h1:IDGUI2N/OS3PiU4qZcXJeWKPI6O/9Y8hOrbSiMcqyYw= github.com/hashicorp/go-getter v1.3.0 h1:pFMSFlI9l5NaeuzkpE3L7BYk9qQ9juTAgXW/H0cqxcU= github.com/hashicorp/go-getter v1.3.0/go.mod h1:/O1k/AizTN0QmfEKknCYGvICeyKUDqCYA8vvWtGWDeQ= @@ -297,14 +354,19 @@ github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.1.0 h1:vN9wG1D6KG6YHRTWr8512cxGOVgTMEfgEdSj/hr8MPc= github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-memdb v1.0.2 h1:AIjzJlwIxz2inhZqRJZfe6D15lPeF0/cZyS1BVlnlHg= github.com/hashicorp/go-memdb v1.0.2/go.mod h1:I6dKdmYhZqU0RJSheVEWgTNWdVQH5QvTgIUQ0t/t32M= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-plugin v1.0.0/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-plugin v1.0.1 h1:4OtAfUGbnKC6yS48p0CtMX2oFYtzFZVv6rok3cRWgnE= github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= +github.com/hashicorp/go-raftchunking v0.6.2 h1:imj6CVkwXj6VzgXZQvzS+fSrkbFCzlJ2t00F3PacnuU= github.com/hashicorp/go-raftchunking v0.6.2/go.mod h1:cGlg3JtDy7qy6c/3Bu660Mic1JF+7lWqIwCFSb08fX0= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE= @@ -323,6 +385,7 @@ github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0S github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= 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/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= @@ -338,30 +401,50 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/nomad/api v0.0.0-20190412184103-1c38ced33adf h1:U/40PQvWkaXCDdK9QHKf1pVDVcA+NIDVbzzonFGkgIA= github.com/hashicorp/nomad/api v0.0.0-20190412184103-1c38ced33adf/go.mod h1:BDngVi1f4UA6aJq9WYTgxhfWSE1+42xshvstLU2fRGk= github.com/hashicorp/raft v1.0.1/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI= +github.com/hashicorp/raft v1.1.1 h1:HJr7UE1x/JrJSc9Oy6aDBHtNHUUBHjcQjTgvUVihoZs= github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= github.com/hashicorp/raft-boltdb v0.0.0-20171010151810-6e5ba93211ea/go.mod h1:pNv7Wc3ycL6F5oOWn+tPGo2gWD4a5X+yp/ntwdKLjRk= +github.com/hashicorp/raft-snapshot v1.0.1 h1:cx002JsTEAfAP0pIuANlDtTXg/pi2Db6YbRRmLQTQKw= github.com/hashicorp/raft-snapshot v1.0.1/go.mod h1:5sL9eUn72lH5DzsFIJ9jaysITbHksSSszImWSOTC8Ic= +github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hashicorp/vault v1.2.3 h1:kTMygKuUEjFSp5Xzfkw7R2ScJcOG4rsAcDnPtdrOBsE= github.com/hashicorp/vault v1.2.3/go.mod h1:3foqP78mwtT8iZoKtBt41XnUpL7/etJxg+GQQX+6p64= +github.com/hashicorp/vault-plugin-auth-alicloud v0.5.2-0.20190814210027-93970f08f2ec h1:HXVE8h6RXFsPJgwWpE+5CscsgekqtX4nhDlZGV9jEe4= github.com/hashicorp/vault-plugin-auth-alicloud v0.5.2-0.20190814210027-93970f08f2ec/go.mod h1:TYFfVFgKF9x92T7uXouI9rLPkNnyXo/KkNcj5t+mjdM= +github.com/hashicorp/vault-plugin-auth-azure v0.5.2-0.20190814210035-08e00d801115 h1:E57y918o+c+NoI5k7ohbpZu7vRm1XZKZfC5VQVpJvDI= github.com/hashicorp/vault-plugin-auth-azure v0.5.2-0.20190814210035-08e00d801115/go.mod h1:sRhTnkcbjJgPeES0ddCTq8S2waSakyMiWLUwO5J/Wjk= +github.com/hashicorp/vault-plugin-auth-centrify v0.5.2-0.20190814210042-090ec2ed93ce h1:X8umWdCqSVk/75ZjEBDxYL+V8i+jK3KbJbFoyOryCww= github.com/hashicorp/vault-plugin-auth-centrify v0.5.2-0.20190814210042-090ec2ed93ce/go.mod h1:WstOCHERNbk2dblnY5MV9Qeh/hzTSQpVs5xPuyAzlBo= +github.com/hashicorp/vault-plugin-auth-cf v0.0.0-20190821162840-1c2205826fee h1:gJG1PJGiqi+0M0HTYlwDyV5CyetLhFl9DxyMJre5H9Y= github.com/hashicorp/vault-plugin-auth-cf v0.0.0-20190821162840-1c2205826fee/go.mod h1:zOag32+pm1R4FFNhXMLP506Oesjoai3gHEEpxqUaTr0= github.com/hashicorp/vault-plugin-auth-gcp v0.5.1/go.mod h1:eLj92eX8MPI4vY1jaazVLF2sVbSAJ3LRHLRhF/pUmlI= +github.com/hashicorp/vault-plugin-auth-gcp v0.5.2-0.20190814210049-1ccb3dc10102 h1:RTHVdxCDwxTq/4zZFkV+b8zexkSU5EOXkY2D/kAvyFU= github.com/hashicorp/vault-plugin-auth-gcp v0.5.2-0.20190814210049-1ccb3dc10102/go.mod h1:j0hMnnTD44zXGQhLM1jarYDaTmSp6OPiOzgFQ6mNgzc= +github.com/hashicorp/vault-plugin-auth-jwt v0.5.2-0.20190815164639-5fa0eef3a023 h1:RMGN5WLZ6QnTGNsDT5jmTf3RO54lOV7JMeveLMngOuk= github.com/hashicorp/vault-plugin-auth-jwt v0.5.2-0.20190815164639-5fa0eef3a023/go.mod h1:Ti2NPndKhSGpSL6gWg11n7TkmuI7318BIPeojayIVRU= +github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.2-0.20190826163451-8461c66275a9 h1:PjbIf3mlPBJopQSJstQAhVbdGTVZ/W35RZtm/GCOTUs= github.com/hashicorp/vault-plugin-auth-kubernetes v0.5.2-0.20190826163451-8461c66275a9/go.mod h1:qkrONCr71ckSCTItJQ1j9uet/faieZJ5c7+GZugTm7s= +github.com/hashicorp/vault-plugin-auth-oci v0.0.0-20190904175623-97c0c0187c5c h1:z6LQZvs1OtoVy2XgbgNhiDgp0U62Xbstn7/cgNZvh6g= github.com/hashicorp/vault-plugin-auth-oci v0.0.0-20190904175623-97c0c0187c5c/go.mod h1:YAl51RsYRihPbSdnug1NsvutzbRVfrZ12FjEIvSiOTs= +github.com/hashicorp/vault-plugin-database-elasticsearch v0.0.0-20190814210117-e079e01fbb93 h1:kXTV1ImOPgDGZxAlbEQfiXgnZY/34vfgnZVhI/tscmg= github.com/hashicorp/vault-plugin-database-elasticsearch v0.0.0-20190814210117-e079e01fbb93/go.mod h1:N9XpfMXjeLHBgUd8iy4avOC4mCSqUC7B/R8AtCYhcfE= +github.com/hashicorp/vault-plugin-secrets-ad v0.5.3-0.20190814210122-0f2fd536b250 h1:+mm2cM5msg/USImbvnMS2yzCMBYMCO3CrvsATWGtHtY= github.com/hashicorp/vault-plugin-secrets-ad v0.5.3-0.20190814210122-0f2fd536b250/go.mod h1:F8hKHqcB7stN2OhnqE3emwFYtKO0IDNxMBbPs2n8vr0= +github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.2-0.20190814210129-4d18bec92f56 h1:PGE26//x1eiAbZ1ExffhKa4y9xgDKLd9BHDZRkOzbEY= github.com/hashicorp/vault-plugin-secrets-alicloud v0.5.2-0.20190814210129-4d18bec92f56/go.mod h1:hJ42zFd3bHyE8O2liBUG+VPY0JxdMrj51TOwVGViUIU= +github.com/hashicorp/vault-plugin-secrets-azure v0.5.2-0.20190814210135-54b8afbc42ae h1:LtRJy7H/9ftjHGo5SMLG8/7DI7CYL1Zur9jBJTyzXg8= github.com/hashicorp/vault-plugin-secrets-azure v0.5.2-0.20190814210135-54b8afbc42ae/go.mod h1:SBc53adxMmf+o8zqRbqYvq+nuSrz8OHYmgmPfxVMJEo= +github.com/hashicorp/vault-plugin-secrets-gcp v0.5.3-0.20190814210141-d2086ff79b04 h1:2FLjwVqpWueSoxaNdcC2Za7RX8FNp8Xt8pF/03dinV4= github.com/hashicorp/vault-plugin-secrets-gcp v0.5.3-0.20190814210141-d2086ff79b04/go.mod h1:Sc+ba3kscakE5a/pi8JJhWvXWok3cpt1P77DApmUuDc= +github.com/hashicorp/vault-plugin-secrets-gcpkms v0.5.2-0.20190814210149-315cdbf5de6e h1:RjQBOFneGwxhHsymNtbEUJXAjMO74GlZcmUrGqJnYxY= github.com/hashicorp/vault-plugin-secrets-gcpkms v0.5.2-0.20190814210149-315cdbf5de6e/go.mod h1:5prAHuCcBiyv+xfGBviTVYeDQUhmQYN7WrxC2gMRWeQ= +github.com/hashicorp/vault-plugin-secrets-kv v0.5.2-0.20190814210155-e060c2a001a8 h1:8nZOMqGQQiuWNld162nxUvM4/7EW4NOO9gpyp7LCC84= github.com/hashicorp/vault-plugin-secrets-kv v0.5.2-0.20190814210155-e060c2a001a8/go.mod h1:5ksi71TrWxz7ZRo0MIlsry2lYDlZQyLalN4cF8a4vnk= github.com/hashicorp/vault/api v1.0.1/go.mod h1:AV/+M5VPDpB90arloVX0rVDUIHkONiwz5Uza9HRtpUE= github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU= @@ -376,22 +459,27 @@ github.com/hashicorp/vault/sdk v0.1.14-0.20190814205504-1cad00d1133b/go.mod h1:B github.com/hashicorp/vault/sdk v0.1.14-0.20190909201848-e0fbf9b652e2 h1:b65cSyZqljnCPzzsUXvR4P0eXypo1xahQyG809+IySk= github.com/hashicorp/vault/sdk v0.1.14-0.20190909201848-e0fbf9b652e2/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c h1:kQWxfPIHVLbgLzphqk3QUflDy9QdksZR4ygR807bpy0= github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c h1:aY2hhxLhjEAbfXOx2nRJxCXezC6CO2V/yN+OCr1srtk= github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 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= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4 h1:3K3KcD4S6/Y2hevi70EzUTNKOS3cryQyhUnkjE6Tz0w= github.com/influxdata/influxdb v0.0.0-20190411212539-d24b7ba8c4c4/go.mod h1:qZna6X/4elxqT3yI9iZYdZrWWdeFOOprn86kgg4+IzY= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jeffchao/backoff v0.0.0-20140404060208-9d7fd7aa17f2 h1:mex1izRBCD+7WjieGgRdy7e651vD/lvB1bD9vNE/3K4= github.com/jeffchao/backoff v0.0.0-20140404060208-9d7fd7aa17f2/go.mod h1:xkfESuHriIekR+4RoV+fu91j/CfnYM29Zi2tMFw5iD4= github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00= +github.com/jefferai/jsonx v1.0.0 h1:Xoz0ZbmkpBvED5W9W1B5B/zc3Oiq7oXqiW7iRV3B6EI= github.com/jefferai/jsonx v1.0.0/go.mod h1:OGmqmi2tTeI/PS+qQfBDToLHHJIy/RMp24fPo8vFvoQ= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE= @@ -402,12 +490,15 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22 github.com/joyent/triton-go v0.0.0-20190112182421-51ffac552869/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/keybase/go-crypto v0.0.0-20190403132359-d65b6b94177f h1:Gsc9mVHLRqBjMgdQCghN9NObCcRncDqxJvBvEaIIQEo= github.com/keybase/go-crypto v0.0.0-20190403132359-d65b6b94177f/go.mod h1:ghbZscTyKdM07+Fw3KSi0hcJm+AlEUWj8QLlPtijN/M= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -416,13 +507,16 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.0.0/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw= github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI= 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= @@ -438,9 +532,12 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp 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= github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/michaelklishin/rabbit-hole v1.5.0 h1:Bex27BiFDsijCM9D0ezSHqyy0kehpYHuNKaPqq/a4RM= github.com/michaelklishin/rabbit-hole v1.5.0/go.mod h1:vvI1uOitYZi0O5HEGXhaWC1XT80Gy+HvFheJ+5Krlhk= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= +github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= @@ -459,11 +556,14 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8 h1:1CO5wil3HuiVLrUQ2ovSTO+6AfNOA5EMkHHVyHE9IwA= github.com/mitchellh/pointerstructure v0.0.0-20190430161007-f252a8fd71c8/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mozilla-services/yaml v0.0.0-20180922153656-28ffe5d0cafb h1:wj4n5+b4t84Qze8N/n0PKpaBTlbA7g7nTYG01h16mh0= github.com/mozilla-services/yaml v0.0.0-20180922153656-28ffe5d0cafb/go.mod h1:Is/Ucts/yU/mWyGR8yELRoO46mejouKsJfQLAIfTR18= @@ -472,24 +572,35 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+ github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/oracle/oci-go-sdk v7.0.0+incompatible h1:oj5ESjXwwkFRdhZSnPlShvLWYdt/IZ65RQxveYM3maA= github.com/oracle/oci-go-sdk v7.0.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35ukwStZIg5F66tcBccjip/j888= +github.com/ory/dockertest v3.3.4+incompatible h1:VrpM6Gqg7CrPm3bL4Wm1skO+zFWLbh7/Xb5kGEbJRh8= github.com/ory/dockertest v3.3.4+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2 h1:CXwSGu/LYmbjEab5aMCs5usQRVBGThelUKBNnoSOuso= github.com/oxtoacart/bpool v0.0.0-20150712133111-4e1c5567d7c2/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= @@ -504,23 +615,29 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/pquerna/otp v1.1.0 h1:q2gMsMuMl3JzneUaAX1MRGxLvOG6bzXV51hivBaStf0= github.com/pquerna/otp v1.1.0/go.mod h1:Zad1CMQfSQZI5KLpahDiSUX4tMMREnXw98IvL1nhgMk= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_model v0.0.0-20170216185247-6f3806018612/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f h1:BVwpUVJDADN2ufcGik7W992pyps0wZ888b/y9GXcLTU= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20180612222113-7d6f385de8be/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 h1:/K3IL0Z1quvmJ7X0A1AwNEK7CRkVK3YwfOU/QAL4WGg= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad h1:j5pg/OewZJyE6i3hIG4v3eQUvUyFdQkC8Nd/mjaEkxE= github.com/r3labs/diff v0.0.0-20190801153147-a71de73c46ad/go.mod h1:ozniNEFS3j1qCwHKdvraMn1WJOsUxHd7lYfukEIS4cs= @@ -533,8 +650,11 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= @@ -565,21 +685,27 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/smartystreets/assertions v0.0.0-20180725160413-e900ae048470/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945 h1:N8Bg45zpk/UcpNGnfJt2y/3lRWASHNTUET8owPYCgYI= github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 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/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94 h1:0ngsPmuP6XIjiFRNFYlvKwSr5zff2v+uPHaffZ6/M4k= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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= @@ -603,6 +729,7 @@ github.com/variantdev/vals v0.0.0-20191026125821-5d18b16cf30a/go.mod h1:8CW8eonQ github.com/variantdev/vals v0.0.0-20191030045026-1fa6af1b5299 h1:91g7EEeE6dHdubLkS013s9vcZScrGDk/RqZEQwSkn4w= github.com/variantdev/vals v0.0.0-20191030045026-1fa6af1b5299/go.mod h1:8CW8eonQlIJgAjF1fLfrkaBe16fGjwGf2PX52/Vualw= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20190412021913-f29b1ada1971/go.mod h1:KSGwdbiFchh5KIC9My2+ZVl5/3ANcwohw50dpPwa2cw= go.mozilla.org/gopgagent v0.0.0-20170926210634-4d7ea76ff71a h1:N7VD+PwpJME2ZfQT8+ejxwA4Ow10IkGbU0MGf94ll8k= @@ -715,6 +842,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -856,19 +984,25 @@ google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= 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/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0= gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/ory-am/dockertest.v3 v3.3.4/go.mod h1:s9mmoLkaGeAh97qygnNj4xWkiN7e1SKekYC6CovU+ek= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= @@ -877,6 +1011,7 @@ gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4 gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A= gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/urfave/cli.v1 v1.20.0/go.mod h1:vuBzUtMdQeixQj8LVd+/98pzhxNGQoyuPBlsXHOQNO0= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= @@ -901,12 +1036,17 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +k8s.io/api v0.0.0-20190409092523-d687e77c8ae9 h1:c9UEl5z8gk1DGh/g3snETZ+a52YeR9VdbX/3BQ4PHas= k8s.io/api v0.0.0-20190409092523-d687e77c8ae9/go.mod h1:FQEUn50aaytlU65qqBn/w+5ugllHwrBzKm7DzbnXdzE= +k8s.io/apimachinery v0.0.0-20190409092423-760d1845f48b h1:fVkKJL9FIpA8LSJyHVM00MP45q1WJ7+af77vcxmQP4g= k8s.io/apimachinery v0.0.0-20190409092423-760d1845f48b/go.mod h1:FW86P8YXVLsbuplGMZeb20J3jYHscrDqw4jELaFJvRU= +k8s.io/klog v0.0.0-20190306015804-8e90cee79f82 h1:SHucoAy7lRb+w5oC/hbXyZg+zX+Wftn6hD4tGzHCVqA= k8s.io/klog v0.0.0-20190306015804-8e90cee79f82/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +layeh.com/radius v0.0.0-20190322222518-890bc1058917 h1:BDXFaFzUt5EIqe/4wrTc4AcYZWP6iC6Ult+jQWLh5eU= layeh.com/radius v0.0.0-20190322222518-890bc1058917/go.mod h1:fywZKyu//X7iRzaxLgPWsvc0L26IUpVvE/aeIL2JtIQ= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/pkg/app/app.go b/pkg/app/app.go index 2745eb47..5bafd47f 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -1,6 +1,7 @@ package app import ( + "bytes" "fmt" "io/ioutil" "log" @@ -8,8 +9,10 @@ import ( "os/signal" "strings" "syscall" + "text/tabwriter" "github.com/gosuri/uitable" + "github.com/roboll/helmfile/pkg/argparser" "github.com/roboll/helmfile/pkg/helmexec" "github.com/roboll/helmfile/pkg/remote" "github.com/roboll/helmfile/pkg/state" @@ -146,7 +149,7 @@ func (a *App) Sync(c SyncConfigProvider) error { func (a *App) Apply(c ApplyConfigProvider) error { return a.ForEachState(func(run *Run) []error { - return run.Apply(c) + return a.apply(run, c) }) } @@ -159,13 +162,13 @@ func (a *App) Status(c StatusesConfigProvider) error { func (a *App) Delete(c DeleteConfigProvider) error { return a.reverse().ForEachState(func(run *Run) []error { return run.Delete(c) - }) + }, true) } func (a *App) Destroy(c DestroyConfigProvider) error { return a.reverse().ForEachState(func(run *Run) []error { return run.Destroy(c) - }) + }, true) } func (a *App) Test(c TestConfigProvider) error { @@ -380,8 +383,10 @@ func (a *App) visitStates(fileOrDir string, defOpts LoadOpts, converge func(*sta if tmplErr != nil { return appError(fmt.Sprintf("failed executing release templates in \"%s\"", f), tmplErr) } + processed, errs := converge(templated, helm) noMatchInHelmfiles = noMatchInHelmfiles && !processed + return context{a, templated}.clean(errs) }) @@ -396,13 +401,13 @@ func (a *App) visitStates(fileOrDir string, defOpts LoadOpts, converge func(*sta return nil } -func (a *App) ForEachState(do func(*Run) []error) error { +func (a *App) ForEachState(do func(*Run) []error, dagEnabled ...bool) error { ctx := NewContext() err := a.VisitDesiredStatesWithReleasesFiltered(a.FileOrDir, func(st *state.HelmState, helm helmexec.Interface) []error { run := NewRun(st, helm, ctx) return do(run) - }) + }, dagEnabled...) if err != nil && a.ErrorHandler != nil { return a.ErrorHandler(err) @@ -411,7 +416,74 @@ func (a *App) ForEachState(do func(*Run) []error) error { return err } -func (a *App) VisitDesiredStatesWithReleasesFiltered(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) []error) error { +func printBatches(batches [][]state.Release) string { + buf := &bytes.Buffer{} + + w := new(tabwriter.Writer) + + w.Init(buf, 0, 1, 1, ' ', 0) + + fmt.Fprintln(w, "GROUP\tRELEASES") + + for i, batch := range batches { + ids := []string{} + for _, r := range batch { + ids = append(ids, state.ReleaseToID(&r.ReleaseSpec)) + } + fmt.Fprintf(w, "%d\t%s\n", i+1, strings.Join(ids, ", ")) + } + + w.Flush() + + return buf.String() +} + +func withDAG(templated *state.HelmState, helm helmexec.Interface, logger *zap.SugaredLogger, reverse bool, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) (bool, []error) { + batches, err := state.PlanReleases(templated.Releases, templated.Selectors, reverse) + if err != nil { + return false, []error{err} + } + + numBatches := len(batches) + + logger.Debugf("processing %d groups of releases in this order:\n%s", numBatches, printBatches(batches)) + + all := true + + for i, batch := range batches { + var targets []state.ReleaseSpec + + for _, marked := range batch { + targets = append(targets, marked.ReleaseSpec) + } + + var releaseIds []string + for _, r := range targets { + releaseIds = append(releaseIds, state.ReleaseToID(&r)) + } + + logger.Debugf("processing releases in group %d/%d: %s", i+1, numBatches, strings.Join(releaseIds, ", ")) + + batchSt := *templated + batchSt.Releases = targets + + processed, errs := converge(&batchSt, helm) + + if errs != nil && len(errs) > 0 { + return false, errs + } + + all = all && processed + } + + return all, nil +} + +type Opts struct { + DAGEnabled bool +} + +func (a *App) visitStatesWithSelectorsAndRemoteSupport(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) (bool, []error)) error { opts := LoadOpts{ Selectors: a.Selectors, } @@ -450,7 +522,11 @@ func (a *App) VisitDesiredStatesWithReleasesFiltered(fileOrDir string, converge a.remote = remote - return a.visitStates(fileOrDir, opts, func(st *state.HelmState, helm helmexec.Interface) (bool, []error) { + return a.visitStates(fileOrDir, opts, converge) +} + +func (a *App) Wrap(converge func(*state.HelmState, helmexec.Interface) []error) func(st *state.HelmState, helm helmexec.Interface) (bool, []error) { + return func(st *state.HelmState, helm helmexec.Interface) (bool, []error) { if len(st.Selectors) > 0 { err := st.FilterReleases() if err != nil { @@ -485,6 +561,20 @@ func (a *App) VisitDesiredStatesWithReleasesFiltered(fileOrDir string, converge processed := len(st.Releases) != 0 && len(errs) == 0 return processed, errs + } +} + +func (a *App) VisitDesiredStatesWithReleasesFiltered(fileOrDir string, converge func(*state.HelmState, helmexec.Interface) []error, dagEnabled ...bool) error { + f := a.Wrap(converge) + + if len(dagEnabled) > 0 && dagEnabled[0] { + return a.visitStatesWithSelectorsAndRemoteSupport(fileOrDir, func(st *state.HelmState, helm helmexec.Interface) (bool, []error) { + return withDAG(st, helm, a.Logger, a.Reverse, f) + }) + } + + return a.visitStatesWithSelectorsAndRemoteSupport(fileOrDir, func(st *state.HelmState, helm helmexec.Interface) (bool, []error) { + return f(st, helm) }) } @@ -560,6 +650,159 @@ func (a *App) findDesiredStateFiles(specifiedPath string) ([]string, error) { return files, nil } +func (a *App) apply(r *Run, c ApplyConfigProvider) []error { + st := r.state + helm := r.helm + ctx := r.ctx + + affectedReleases := state.AffectedReleases{} + if !c.SkipDeps() { + if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 { + return errs + } + if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 { + return errs + } + } + if errs := st.PrepareReleases(helm, "apply"); errs != nil && len(errs) > 0 { + return errs + } + + // helm must be 2.11+ and helm-diff should be provided `--detailed-exitcode` in order for `helmfile apply` to work properly + detailedExitCode := true + + diffOpts := &state.DiffOpts{ + NoColor: c.NoColor(), + Context: c.Context(), + Set: c.Set(), + } + + changedReleases, errs := st.DiffReleases(helm, c.Values(), c.Concurrency(), detailedExitCode, c.SuppressSecrets(), false, diffOpts) + + deletingReleases, err := st.DetectReleasesToBeDeleted(helm, st.Releases) + if err != nil { + errs = append(errs, err) + } + + fatalErrs := []error{} + + noError := true + for _, e := range errs { + switch err := e.(type) { + case *state.ReleaseError: + if err.Code != 2 { + noError = false + fatalErrs = append(fatalErrs, e) + } + default: + noError = false + fatalErrs = append(fatalErrs, e) + } + } + + releasesToBeDeleted := map[string]state.ReleaseSpec{} + for _, r := range deletingReleases { + id := state.ReleaseToID(&r) + releasesToBeDeleted[id] = r + } + + releasesToBeUpdated := map[string]state.ReleaseSpec{} + for _, r := range changedReleases { + id := state.ReleaseToID(&r) + + // If `helm-diff` detected changes but it is not being `helm delete`ed, we should run `helm upgrade` + if _, ok := releasesToBeDeleted[id]; !ok { + releasesToBeUpdated[id] = r + } + } + + // sync only when there are changes + if noError { + if len(releasesToBeUpdated) == 0 && len(releasesToBeDeleted) == 0 { + // TODO better way to get the logger + logger := c.Logger() + logger.Infof("") + logger.Infof("No affected releases") + } else { + names := []string{} + for _, r := range releasesToBeUpdated { + names = append(names, fmt.Sprintf(" %s (%s) UPDATED", r.Name, r.Chart)) + } + for _, r := range releasesToBeDeleted { + names = append(names, fmt.Sprintf(" %s (%s) DELETED", r.Name, r.Chart)) + } + // Make the output deterministic for testing purpose + sort.Strings(names) + + infoMsg := fmt.Sprintf(`Affected releases are: +%s +`, strings.Join(names, "\n")) + confMsg := fmt.Sprintf(`%s +Do you really want to apply? + Helmfile will apply all your changes, as shown above. + +`, infoMsg) + interactive := c.Interactive() + if !interactive { + a.Logger.Debug(infoMsg) + } + if !interactive || interactive && r.askForConfirmation(confMsg) { + r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...) + + // We deleted releases by traversing the DAG in reverse order + if len(releasesToBeDeleted) > 0 { + _, errs := withDAG(st, helm, a.Logger, true, a.Wrap(func(subst *state.HelmState, helm helmexec.Interface) []error { + var rs []state.ReleaseSpec + + for _, r := range subst.Releases { + if _, ok := releasesToBeDeleted[state.ReleaseToID(&r)]; ok { + rs = append(rs, r) + } + } + + subst.Releases = rs + + return subst.DeleteReleasesForSync(&affectedReleases, helm, c.Concurrency()) + })) + + if errs != nil && len(errs) > 0 { + return errs + } + } + + // We upgrade releases by traversing the DAG + if len(releasesToBeUpdated) > 0 { + _, errs := withDAG(st, helm, a.Logger, false, a.Wrap(func(subst *state.HelmState, helm helmexec.Interface) []error { + var rs []state.ReleaseSpec + + for _, r := range subst.Releases { + if _, ok := releasesToBeUpdated[state.ReleaseToID(&r)]; ok { + rs = append(rs, r) + } + } + + subst.Releases = rs + + syncOpts := state.SyncOpts{ + Set: c.Set(), + } + return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), &syncOpts) + })) + + if errs != nil && len(errs) > 0 { + return errs + } + } + + return nil + } + } + } + + affectedReleases.DisplayAffectedReleases(c.Logger()) + return fatalErrs +} + func fileExistsAt(path string) bool { fileInfo, err := os.Stat(path) return err == nil && fileInfo.Mode().IsRegular() diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index 3fa45dbc..4fbf1d3a 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -1,6 +1,7 @@ package app import ( + "bufio" "bytes" "fmt" "io" @@ -9,10 +10,12 @@ import ( "path/filepath" "reflect" "regexp" + "runtime" "strings" "sync" "testing" + "github.com/roboll/helmfile/pkg/exectest" "gotest.tools/assert" "github.com/roboll/helmfile/pkg/helmexec" @@ -339,6 +342,14 @@ releases: } } +type ctxLogger struct { + label string +} + +func (cl *ctxLogger) Write(b []byte) (int, error) { + return os.Stderr.Write(append([]byte(cl.label+":"), b...)) +} + // See https://github.com/roboll/helmfile/issues/322 func TestVisitDesiredStatesWithReleasesFiltered_Selectors(t *testing.T) { files := map[string]string{ @@ -399,39 +410,43 @@ releases: {label: "duplicatedOK=yes", expectedCount: 2, expectErr: false}, } - for _, testcase := range testcases { - actual := []string{} + for i := range testcases { + testcase := testcases[i] - collectReleases := func(st *state.HelmState, helm helmexec.Interface) []error { - for _, r := range st.Releases { - actual = append(actual, r.Name) + t.Run(testcase.label, func(t *testing.T) { + actual := []string{} + + collectReleases := func(st *state.HelmState, helm helmexec.Interface) []error { + for _, r := range st.Releases { + actual = append(actual, r.Name) + } + return []error{} } - return []error{} - } - app := appWithFs(&App{ - KubeContext: "default", - Logger: helmexec.NewLogger(os.Stderr, "debug"), - Namespace: "", - Selectors: []string{testcase.label}, - Env: "default", - }, files) + app := appWithFs(&App{ + KubeContext: "default", + Logger: helmexec.NewLogger(&ctxLogger{label: testcase.label}, "debug"), + Namespace: "", + Selectors: []string{testcase.label}, + Env: "default", + }, files) - err := app.VisitDesiredStatesWithReleasesFiltered( - "helmfile.yaml", collectReleases, - ) - if testcase.expectErr { - if err == nil { - t.Errorf("error expected but not happened for selector %s", testcase.label) - } else if err.Error() != testcase.errMsg { - t.Errorf("unexpected error message: expected=\"%s\", actual=\"%s\"", testcase.errMsg, err.Error()) + err := app.VisitDesiredStatesWithReleasesFiltered( + "helmfile.yaml", collectReleases, + ) + if testcase.expectErr { + if err == nil { + t.Errorf("error expected but not happened for selector %s", testcase.label) + } else if err.Error() != testcase.errMsg { + t.Errorf("unexpected error message: expected=\"%s\", actual=\"%s\"", testcase.errMsg, err.Error()) + } + } else if !testcase.expectErr && err != nil { + t.Errorf("unexpected error for selector %s: %v", testcase.label, err) } - } else if !testcase.expectErr && err != nil { - t.Errorf("unexpected error for selector %s: %v", testcase.label, err) - } - if len(actual) != testcase.expectedCount { - t.Errorf("unexpected release count for selector %s: expected=%d, actual=%d", testcase.label, testcase.expectedCount, len(actual)) - } + if len(actual) != testcase.expectedCount { + t.Errorf("unexpected release count for selector %s: expected=%d, actual=%d", testcase.label, testcase.expectedCount, len(actual)) + } + }) } } @@ -1838,6 +1853,59 @@ func (c configImpl) Concurrency() int { return 1 } +type applyConfig struct { + args string + values []string + set []string + skipDeps bool + suppressSecrets bool + noColor bool + context int + concurrency int + interactive bool + logger *zap.SugaredLogger +} + +func (a applyConfig) Args() string { + return a.args +} + +func (a applyConfig) Values() []string { + return a.values +} + +func (a applyConfig) Set() []string { + return a.set +} + +func (a applyConfig) SkipDeps() bool { + return a.skipDeps +} + +func (a applyConfig) SuppressSecrets() bool { + return a.suppressSecrets +} + +func (a applyConfig) NoColor() bool { + return a.noColor +} + +func (a applyConfig) Context() int { + return a.context +} + +func (a applyConfig) Concurrency() int { + return a.concurrency +} + +func (a applyConfig) Interactive() bool { + return a.interactive +} + +func (a applyConfig) Logger() *zap.SugaredLogger { + return a.logger +} + // Mocking the command-line runner type mockRunner struct { @@ -1909,9 +1977,11 @@ func (helm *mockHelmExec) ReleaseStatus(context helmexec.HelmContext, release st func (helm *mockHelmExec) DeleteRelease(context helmexec.HelmContext, name string, flags ...string) error { return nil } + func (helm *mockHelmExec) List(context helmexec.HelmContext, filter string, flags ...string) (string, error) { return "", nil } + func (helm *mockHelmExec) DecryptSecret(context helmexec.HelmContext, name string, flags ...string) (string, error) { return "", nil } @@ -1983,6 +2053,1110 @@ releases: } } +func TestApply(t *testing.T) { + testcases := []struct { + name string + loc string + ns string + concurrency int + error string + files map[string]string + lists map[exectest.ListKey]string + diffs map[exectest.DiffKey]error + upgraded []exectest.Release + deleted []exectest.Release + log string + }{ + // + // complex test cases for smoke testing + // + { + name: "smoke", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: database + chart: charts/mysql + needs: + - logging +- name: frontend-v1 + chart: charts/frontend + installed: false + needs: + - servicemesh + - logging + - backend-v1 +- name: frontend-v2 + chart: charts/frontend + needs: + - servicemesh + - logging + - backend-v2 +- name: frontend-v3 + chart: charts/frontend + needs: + - servicemesh + - logging + - backend-v2 +- name: backend-v1 + chart: charts/backend + installed: false + needs: + - servicemesh + - logging + - database + - anotherbackend +- name: backend-v2 + chart: charts/backend + needs: + - servicemesh + - logging + - database + - anotherbackend +- name: anotherbackend + chart: charts/anotherbackend + needs: + - servicemesh + - logging + - database +- name: servicemesh + chart: charts/istio + needs: + - logging +- name: logging + chart: charts/fluent-bit +- name: front-proxy + chart: stable/envoy +`, + }, + diffs: map[exectest.DiffKey]error{ + // noop on frontend-v2 + exectest.DiffKey{Name: "frontend-v2", Chart: "charts/frontend", Flags: "--kube-contextdefault--detailed-exitcode"}: nil, + // install frontend-v3 + exectest.DiffKey{Name: "frontend-v3", Chart: "charts/frontend", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + // upgrades + exectest.DiffKey{Name: "logging", Chart: "charts/fluent-bit", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "front-proxy", Chart: "stable/envoy", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "servicemesh", Chart: "charts/istio", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "database", Chart: "charts/mysql", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "backend-v2", Chart: "charts/backend", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "anotherbackend", Chart: "charts/anotherbackend", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + lists: map[exectest.ListKey]string{ + // delete frontend-v1 and backend-v1 + exectest.ListKey{Filter: "^frontend-v1$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +frontend-v1 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default +`, + exectest.ListKey{Filter: "^backend-v1$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +backend-v1 4 Fri Nov 1 08:40:07 2019 DEPLOYED backend-3.1.0 3.1.0 default +`, + }, + // Disable concurrency to avoid in-deterministic result + concurrency: 1, + upgraded: []exectest.Release{ + {Name: "front-proxy", Flags: []string{}}, + {Name: "logging", Flags: []string{}}, + {Name: "database", Flags: []string{}}, + {Name: "servicemesh", Flags: []string{}}, + {Name: "anotherbackend", Flags: []string{}}, + {Name: "backend-v2", Flags: []string{}}, + {Name: "frontend-v3", Flags: []string{}}, + }, + deleted: []exectest.Release{ + {Name: "frontend-v1", Flags: []string{}}, + }, + log: `processing file "helmfile.yaml" in directory "." +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: database + 3: chart: charts/mysql + 4: needs: + 5: - logging + 6: - name: frontend-v1 + 7: chart: charts/frontend + 8: installed: false + 9: needs: +10: - servicemesh +11: - logging +12: - backend-v1 +13: - name: frontend-v2 +14: chart: charts/frontend +15: needs: +16: - servicemesh +17: - logging +18: - backend-v2 +19: - name: frontend-v3 +20: chart: charts/frontend +21: needs: +22: - servicemesh +23: - logging +24: - backend-v2 +25: - name: backend-v1 +26: chart: charts/backend +27: installed: false +28: needs: +29: - servicemesh +30: - logging +31: - database +32: - anotherbackend +33: - name: backend-v2 +34: chart: charts/backend +35: needs: +36: - servicemesh +37: - logging +38: - database +39: - anotherbackend +40: - name: anotherbackend +41: chart: charts/anotherbackend +42: needs: +43: - servicemesh +44: - logging +45: - database +46: - name: servicemesh +47: chart: charts/istio +48: needs: +49: - logging +50: - name: logging +51: chart: charts/fluent-bit +52: - name: front-proxy +53: chart: stable/envoy +54: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: database + 3: chart: charts/mysql + 4: needs: + 5: - logging + 6: - name: frontend-v1 + 7: chart: charts/frontend + 8: installed: false + 9: needs: +10: - servicemesh +11: - logging +12: - backend-v1 +13: - name: frontend-v2 +14: chart: charts/frontend +15: needs: +16: - servicemesh +17: - logging +18: - backend-v2 +19: - name: frontend-v3 +20: chart: charts/frontend +21: needs: +22: - servicemesh +23: - logging +24: - backend-v2 +25: - name: backend-v1 +26: chart: charts/backend +27: installed: false +28: needs: +29: - servicemesh +30: - logging +31: - database +32: - anotherbackend +33: - name: backend-v2 +34: chart: charts/backend +35: needs: +36: - servicemesh +37: - logging +38: - database +39: - anotherbackend +40: - name: anotherbackend +41: chart: charts/anotherbackend +42: needs: +43: - servicemesh +44: - logging +45: - database +46: - name: servicemesh +47: chart: charts/istio +48: needs: +49: - logging +50: - name: logging +51: chart: charts/fluent-bit +52: - name: front-proxy +53: chart: stable/envoy +54: + +merged environment: &{default map[] map[]} +worker 1/1 started +worker 1/1 finished +worker 1/1 started +worker 1/1 finished +Affected releases are: + anotherbackend (charts/anotherbackend) UPDATED + backend-v1 (charts/backend) DELETED + backend-v2 (charts/backend) UPDATED + database (charts/mysql) UPDATED + front-proxy (stable/envoy) UPDATED + frontend-v1 (charts/frontend) DELETED + frontend-v3 (charts/frontend) UPDATED + logging (charts/fluent-bit) UPDATED + servicemesh (charts/istio) UPDATED + +processing 5 groups of releases in this order: +GROUP RELEASES +1 frontend-v1, frontend-v2, frontend-v3 +2 backend-v1, backend-v2 +3 anotherbackend +4 database, servicemesh +5 front-proxy, logging + +processing releases in group 1/5: frontend-v1, frontend-v2, frontend-v3 +worker 1/1 started +worker 1/1 finished +processing releases in group 2/5: backend-v1, backend-v2 +worker 1/1 started +worker 1/1 finished +processing releases in group 3/5: anotherbackend +processing releases in group 4/5: database, servicemesh +processing releases in group 5/5: front-proxy, logging +processing 5 groups of releases in this order: +GROUP RELEASES +1 front-proxy, logging +2 database, servicemesh +3 anotherbackend +4 backend-v1, backend-v2 +5 frontend-v1, frontend-v2, frontend-v3 + +processing releases in group 1/5: front-proxy, logging +worker 1/1 started +worker 1/1 finished +worker 1/1 started +getting deployed release version failed:unexpected list key: {^front-proxy$ --kube-contextdefault} +getting deployed release version failed:unexpected list key: {^logging$ --kube-contextdefault} +worker 1/1 finished +processing releases in group 2/5: database, servicemesh +worker 1/1 started +worker 1/1 finished +worker 1/1 started +getting deployed release version failed:unexpected list key: {^database$ --kube-contextdefault} +getting deployed release version failed:unexpected list key: {^servicemesh$ --kube-contextdefault} +worker 1/1 finished +processing releases in group 3/5: anotherbackend +worker 1/1 started +worker 1/1 finished +worker 1/1 started +getting deployed release version failed:unexpected list key: {^anotherbackend$ --kube-contextdefault} +worker 1/1 finished +processing releases in group 4/5: backend-v1, backend-v2 +worker 1/1 started +worker 1/1 finished +worker 1/1 started +getting deployed release version failed:unexpected list key: {^backend-v2$ --kube-contextdefault} +worker 1/1 finished +processing releases in group 5/5: frontend-v1, frontend-v2, frontend-v3 +worker 1/1 started +worker 1/1 finished +worker 1/1 started +getting deployed release version failed:unexpected list key: {^frontend-v3$ --kube-contextdefault} +worker 1/1 finished +`, + }, + // + // noop: no changes + // + { + name: "noop", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: bar + chart: mychart2 +- name: foo + chart: mychart1 + installed: false + needs: + - bar +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: nil, + }, + lists: map[exectest.ListKey]string{ + exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault"}: ``, + exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default +`, + }, + upgraded: []exectest.Release{}, + deleted: []exectest.Release{}, + }, + // + // install + // + { + name: "install", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: baz + chart: mychart3 +- name: foo + chart: mychart1 + needs: + - bar +- name: bar + chart: mychart2 +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "baz", Chart: "mychart3", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + lists: map[exectest.ListKey]string{}, + upgraded: []exectest.Release{ + {Name: "bar", Flags: []string{}}, + {Name: "baz", Flags: []string{}}, + {Name: "foo", Flags: []string{}}, + }, + deleted: []exectest.Release{}, + concurrency: 1, + log: `processing file "helmfile.yaml" in directory "." +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: baz + 3: chart: mychart3 + 4: - name: foo + 5: chart: mychart1 + 6: needs: + 7: - bar + 8: - name: bar + 9: chart: mychart2 +10: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: baz + 3: chart: mychart3 + 4: - name: foo + 5: chart: mychart1 + 6: needs: + 7: - bar + 8: - name: bar + 9: chart: mychart2 +10: + +merged environment: &{default map[] map[]} +worker 1/1 started +worker 1/1 finished +worker 1/1 started +worker 1/1 finished +Affected releases are: + bar (mychart2) UPDATED + baz (mychart3) UPDATED + foo (mychart1) UPDATED + +processing 2 groups of releases in this order: +GROUP RELEASES +1 bar, baz +2 foo + +processing releases in group 1/2: bar, baz +worker 1/1 started +worker 1/1 finished +worker 1/1 started +getting deployed release version failed:unexpected list key: {^bar$ --kube-contextdefault} +getting deployed release version failed:unexpected list key: {^baz$ --kube-contextdefault} +worker 1/1 finished +processing releases in group 2/2: foo +worker 1/1 started +worker 1/1 finished +worker 1/1 started +getting deployed release version failed:unexpected list key: {^foo$ --kube-contextdefault} +worker 1/1 finished +`, + }, + // + // upgrades + // + { + name: "upgrade when foo needs bar", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: bar + chart: mychart2 +- name: foo + chart: mychart1 + needs: + - bar +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + upgraded: []exectest.Release{ + {Name: "bar", Flags: []string{}}, + {Name: "foo", Flags: []string{}}, + }, + }, + { + name: "upgrade when bar needs foo", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: foo + chart: mychart1 +- name: bar + chart: mychart2 + needs: + - foo +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + upgraded: []exectest.Release{ + {Name: "foo", Flags: []string{}}, + {Name: "bar", Flags: []string{}}, + }, + }, + { + name: "upgrade when foo needs bar, with ns override", + loc: location(), + ns: "testNamespace", + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: bar + chart: mychart2 +- name: foo + chart: mychart1 + needs: + - bar +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + upgraded: []exectest.Release{ + {Name: "bar", Flags: []string{}}, + {Name: "foo", Flags: []string{}}, + }, + }, + { + name: "upgrade when bar needs foo, with ns override", + loc: location(), + ns: "testNamespace", + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: foo + chart: mychart1 +- name: bar + chart: mychart2 + needs: + - foo +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacetestNamespace--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + upgraded: []exectest.Release{ + {Name: "foo", Flags: []string{}}, + {Name: "bar", Flags: []string{}}, + }, + }, + { + name: "upgrade when ns1/foo needs ns2/bar", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: foo + chart: mychart1 + namespace: ns1 + needs: + - ns2/bar +- name: bar + chart: mychart2 + namespace: ns2 +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + upgraded: []exectest.Release{ + {Name: "bar", Flags: []string{"--kube-context", "default", "--namespace", "ns2"}}, + {Name: "foo", Flags: []string{"--kube-context", "default", "--namespace", "ns1"}}, + }, + }, + { + name: "upgrade when ns2/bar needs ns1/foo", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: bar + chart: mychart2 + namespace: ns2 + needs: + - ns1/foo +- name: foo + chart: mychart1 + namespace: ns1 +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + upgraded: []exectest.Release{ + {Name: "foo", Flags: []string{"--kube-context", "default", "--namespace", "ns1"}}, + {Name: "bar", Flags: []string{"--kube-context", "default", "--namespace", "ns2"}}, + }, + }, + { + name: "upgrade when tns1/ns1/foo needs tns2/ns2/bar", + loc: location(), + + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: foo + chart: mychart1 + namespace: ns1 + tillerNamespace: tns1 + needs: + - tns2/ns2/bar +- name: bar + chart: mychart2 + namespace: ns2 + tillerNamespace: tns2 +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--tiller-namespacetns2--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--tiller-namespacetns1--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + upgraded: []exectest.Release{ + {Name: "bar", Flags: []string{"--tiller-namespace", "tns2", "--kube-context", "default", "--namespace", "ns2"}}, + {Name: "foo", Flags: []string{"--tiller-namespace", "tns1", "--kube-context", "default", "--namespace", "ns1"}}, + }, + }, + { + name: "upgrade when tns2/ns2/bar needs tns1/ns1/foo", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: bar + chart: mychart2 + namespace: ns2 + tillerNamespace: tns2 + needs: + - tns1/ns1/foo +- name: foo + chart: mychart1 + namespace: ns1 + tillerNamespace: tns1 +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--tiller-namespacetns2--kube-contextdefault--namespacens2--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--tiller-namespacetns1--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + upgraded: []exectest.Release{ + {Name: "foo", Flags: []string{"--tiller-namespace", "tns1", "--kube-context", "default", "--namespace", "ns1"}}, + {Name: "bar", Flags: []string{"--tiller-namespace", "tns2", "--kube-context", "default", "--namespace", "ns2"}}, + }, + // as we check for log output, set concurrency to 1 to avoid non-deterministic test result + concurrency: 1, + log: `processing file "helmfile.yaml" in directory "." +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: bar + 3: chart: mychart2 + 4: namespace: ns2 + 5: tillerNamespace: tns2 + 6: needs: + 7: - tns1/ns1/foo + 8: - name: foo + 9: chart: mychart1 +10: namespace: ns1 +11: tillerNamespace: tns1 +12: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: bar + 3: chart: mychart2 + 4: namespace: ns2 + 5: tillerNamespace: tns2 + 6: needs: + 7: - tns1/ns1/foo + 8: - name: foo + 9: chart: mychart1 +10: namespace: ns1 +11: tillerNamespace: tns1 +12: + +merged environment: &{default map[] map[]} +worker 1/1 started +worker 1/1 finished +worker 1/1 started +worker 1/1 finished +Affected releases are: + bar (mychart2) UPDATED + foo (mychart1) UPDATED + +processing 2 groups of releases in this order: +GROUP RELEASES +1 tns1/ns1/foo +2 tns2/ns2/bar + +processing releases in group 1/2: tns1/ns1/foo +worker 1/1 started +worker 1/1 finished +worker 1/1 started +getting deployed release version failed:unexpected list key: {^foo$ --tiller-namespacetns1--kube-contextdefault} +worker 1/1 finished +processing releases in group 2/2: tns2/ns2/bar +worker 1/1 started +worker 1/1 finished +worker 1/1 started +getting deployed release version failed:unexpected list key: {^bar$ --tiller-namespacetns2--kube-contextdefault} +worker 1/1 finished +`, + }, + // + // deletes: deleting all releases in the correct order + // + { + name: "delete foo and bar when foo needs bar", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: bar + chart: mychart2 + installed: false +- name: foo + chart: mychart1 + installed: false + needs: + - bar +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + lists: map[exectest.ListKey]string{ + exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default +`, + exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default +`, + }, + deleted: []exectest.Release{ + {Name: "foo", Flags: []string{}}, + {Name: "bar", Flags: []string{}}, + }, + }, + { + name: "delete foo and bar when bar needs foo", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: bar + chart: mychart2 + installed: false + needs: + - foo +- name: foo + chart: mychart1 + installed: false +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + lists: map[exectest.ListKey]string{ + exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default +`, + exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default +`, + }, + deleted: []exectest.Release{ + {Name: "bar", Flags: []string{}}, + {Name: "foo", Flags: []string{}}, + }, + }, + // + // upgrade and delete: upgrading one while deleting another + // + { + name: "delete foo when foo needs bar", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: bar + chart: mychart2 +- name: foo + chart: mychart1 + installed: false + needs: + - bar +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + lists: map[exectest.ListKey]string{ + exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default +`, + exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default +`, + }, + upgraded: []exectest.Release{ + {Name: "bar", Flags: []string{}}, + }, + deleted: []exectest.Release{ + {Name: "foo", Flags: []string{}}, + }, + }, + { + name: "delete bar when foo needs bar", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: bar + chart: mychart2 + installed: false +- name: foo + chart: mychart1 + needs: + - bar +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + lists: map[exectest.ListKey]string{ + exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default +`, + exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default +`, + }, + upgraded: []exectest.Release{ + {Name: "foo", Flags: []string{}}, + }, + deleted: []exectest.Release{ + {Name: "bar", Flags: []string{}}, + }, + }, + { + name: "delete foo when bar needs foo", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: foo + chart: mychart1 + installed: false +- name: bar + chart: mychart2 + needs: + - foo +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + lists: map[exectest.ListKey]string{ + exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default +`, + exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default +`, + }, + upgraded: []exectest.Release{ + {Name: "bar", Flags: []string{}}, + }, + deleted: []exectest.Release{ + {Name: "foo", Flags: []string{}}, + }, + }, + { + name: "delete bar when bar needs foo", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: foo + chart: mychart1 +- name: bar + chart: mychart2 + installed: false + needs: + - foo +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "bar", Chart: "mychart2", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + lists: map[exectest.ListKey]string{ + exectest.ListKey{Filter: "^foo$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart1-3.1.0 3.1.0 default +`, + exectest.ListKey{Filter: "^bar$", Flags: "--kube-contextdefault"}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default +`, + }, + upgraded: []exectest.Release{ + {Name: "foo", Flags: []string{}}, + }, + deleted: []exectest.Release{ + {Name: "bar", Flags: []string{}}, + }, + }, + // + // error cases + // + { + name: "non-existent release in needs", + loc: location(), + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: baz + namespace: ns1 + chart: mychart3 +- name: foo + chart: mychart1 + needs: + - bar +`, + }, + diffs: map[exectest.DiffKey]error{ + exectest.DiffKey{Name: "baz", Chart: "mychart3", Flags: "--kube-contextdefault--namespacens1--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + exectest.DiffKey{Name: "foo", Chart: "mychart1", Flags: "--kube-contextdefault--detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + lists: map[exectest.ListKey]string{}, + upgraded: []exectest.Release{}, + deleted: []exectest.Release{}, + concurrency: 1, + error: `in ./helmfile.yaml: "foo" has dependency to inexistent release "bar"`, + log: `processing file "helmfile.yaml" in directory "." +first-pass rendering starting for "helmfile.yaml.part.0": inherited=&{default map[] map[]}, overrode= +first-pass uses: &{default map[] map[]} +first-pass rendering output of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: baz + 3: namespace: ns1 + 4: chart: mychart3 + 5: - name: foo + 6: chart: mychart1 + 7: needs: + 8: - bar + 9: + +first-pass produced: &{default map[] map[]} +first-pass rendering result of "helmfile.yaml.part.0": {default map[] map[]} +vals: +map[] +defaultVals:[] +second-pass rendering result of "helmfile.yaml.part.0": + 0: + 1: releases: + 2: - name: baz + 3: namespace: ns1 + 4: chart: mychart3 + 5: - name: foo + 6: chart: mychart1 + 7: needs: + 8: - bar + 9: + +merged environment: &{default map[] map[]} +worker 1/1 started +worker 1/1 finished +worker 1/1 started +worker 1/1 finished +Affected releases are: + baz (mychart3) UPDATED + foo (mychart1) UPDATED + +err: "foo" has dependency to inexistent release "bar" +`, + }, + } + + for i := range testcases { + tc := testcases[i] + t.Run(tc.name, func(t *testing.T) { + wantUpgrades := tc.upgraded + wantDeletes := tc.deleted + + var helm = &exectest.Helm{ + FailOnUnexpectedList: true, + FailOnUnexpectedDiff: true, + Lists: tc.lists, + Diffs: tc.diffs, + DiffMutex: &sync.Mutex{}, + ChartsMutex: &sync.Mutex{}, + ReleasesMutex: &sync.Mutex{}, + } + + bs := &bytes.Buffer{} + + func() { + logReader, logWriter := io.Pipe() + + logFlushed := &sync.WaitGroup{} + // Ensure all the log is consumed into `bs` by calling `logWriter.Close()` followed by `logFlushed.Wait()` + logFlushed.Add(1) + go func() { + scanner := bufio.NewScanner(logReader) + for scanner.Scan() { + bs.Write(scanner.Bytes()) + bs.WriteString("\n") + } + logFlushed.Done() + }() + + defer func() { + // This is here to avoid data-trace on bytes buffer `bs` to capture logs + if err := logWriter.Close(); err != nil { + panic(err) + } + logFlushed.Wait() + }() + + logger := helmexec.NewLogger(logWriter, "debug") + + valsRuntime, err := vals.New(32) + if err != nil { + t.Errorf("unexpected error creating vals runtime: %v", err) + } + + app := appWithFs(&App{ + glob: filepath.Glob, + abs: filepath.Abs, + KubeContext: "default", + Env: "default", + Logger: logger, + helmExecer: helm, + valsRuntime: valsRuntime, + }, tc.files) + + if tc.ns != "" { + app.Namespace = tc.ns + } + + applyErr := app.Apply(applyConfig{ + // if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic. + concurrency: tc.concurrency, + logger: logger, + }) + if tc.error == "" && applyErr != nil { + t.Fatalf("unexpected error for data defined at %s: %v", tc.loc, applyErr) + } else if tc.error != "" && applyErr == nil { + t.Fatalf("expected error did not occur for data defined at %s", tc.loc) + } else if tc.error != "" && applyErr != nil && tc.error != applyErr.Error() { + t.Fatalf("invalid error: expected %q, got %q", tc.error, applyErr.Error()) + } + + if len(wantUpgrades) > len(helm.Releases) { + t.Fatalf("insufficient number of upgrades: got %d, want %d", len(helm.Releases), len(wantUpgrades)) + } + + for relIdx := range wantUpgrades { + if wantUpgrades[relIdx].Name != helm.Releases[relIdx].Name { + t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Releases[relIdx].Name, wantUpgrades[relIdx].Name) + } + for flagIdx := range wantUpgrades[relIdx].Flags { + if wantUpgrades[relIdx].Flags[flagIdx] != helm.Releases[relIdx].Flags[flagIdx] { + t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Releases[relIdx].Flags[flagIdx], wantUpgrades[relIdx].Flags[flagIdx]) + } + } + } + + if len(wantDeletes) > len(helm.Deleted) { + t.Fatalf("insufficient number of deletes: got %d, want %d", len(helm.Deleted), len(wantDeletes)) + } + + for relIdx := range wantDeletes { + if wantDeletes[relIdx].Name != helm.Deleted[relIdx].Name { + t.Errorf("releases[%d].name: got %q, want %q", relIdx, helm.Deleted[relIdx].Name, wantDeletes[relIdx].Name) + } + for flagIdx := range wantDeletes[relIdx].Flags { + if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] { + t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx]) + } + } + } + }() + + if tc.log != "" { + actual := bs.String() + + diff, exists := testhelper.Diff(tc.log, actual, 3) + if exists { + t.Errorf("unexpected log for data defined %s:\nDIFF\n%s\nEOD", tc.loc, diff) + } + } + }) + } +} + func captureStdout(f func()) string { reader, writer, err := os.Pipe() if err != nil { @@ -2139,3 +3313,8 @@ myrelease4 true id:myrelease1 ` assert.Equal(t, expected, out) } + +func location() string { + _, fn, line, _ := runtime.Caller(1) + return fmt.Sprintf("%s:%d", filepath.Base(fn), line) +} diff --git a/pkg/app/desired_state_file_loader.go b/pkg/app/desired_state_file_loader.go index 9c029f93..31932a9d 100644 --- a/pkg/app/desired_state_file_loader.go +++ b/pkg/app/desired_state_file_loader.go @@ -76,10 +76,10 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e } if ld.namespace != "" { - if st.Namespace != "" { + if st.OverrideNamespace != "" { return nil, errors.New("err: Cannot use option --namespace and set attribute namespace.") } - st.Namespace = ld.namespace + st.OverrideNamespace = ld.namespace } return st, nil diff --git a/pkg/app/run.go b/pkg/app/run.go index 94477cb8..3a8bcbbf 100644 --- a/pkg/app/run.go +++ b/pkg/app/run.go @@ -118,104 +118,6 @@ Do you really want to delete? return errs } -func (r *Run) Apply(c ApplyConfigProvider) []error { - st := r.state - helm := r.helm - ctx := r.ctx - - affectedReleases := state.AffectedReleases{} - if !c.SkipDeps() { - if errs := ctx.SyncReposOnce(st, helm); errs != nil && len(errs) > 0 { - return errs - } - if errs := st.BuildDeps(helm); errs != nil && len(errs) > 0 { - return errs - } - } - if errs := st.PrepareReleases(helm, "apply"); errs != nil && len(errs) > 0 { - return errs - } - - // helm must be 2.11+ and helm-diff should be provided `--detailed-exitcode` in order for `helmfile apply` to work properly - detailedExitCode := true - - diffOpts := &state.DiffOpts{ - NoColor: c.NoColor(), - Context: c.Context(), - Set: c.Set(), - } - - releases, errs := st.DiffReleases(helm, c.Values(), c.Concurrency(), detailedExitCode, c.SuppressSecrets(), false, diffOpts) - - releasesToBeDeleted, err := st.DetectReleasesToBeDeleted(helm) - if err != nil { - errs = append(errs, err) - } - - fatalErrs := []error{} - - noError := true - for _, e := range errs { - switch err := e.(type) { - case *state.ReleaseError: - if err.Code != 2 { - noError = false - fatalErrs = append(fatalErrs, e) - } - default: - noError = false - fatalErrs = append(fatalErrs, e) - } - } - - // sync only when there are changes - if noError { - if len(releases) == 0 && len(releasesToBeDeleted) == 0 { - // TODO better way to get the logger - logger := c.Logger() - logger.Infof("") - logger.Infof("No affected releases") - } else { - names := []string{} - for _, r := range releases { - names = append(names, fmt.Sprintf(" %s (%s) UPDATED", r.Name, r.Chart)) - } - for _, r := range releasesToBeDeleted { - names = append(names, fmt.Sprintf(" %s (%s) DELETED", r.Name, r.Chart)) - } - - msg := fmt.Sprintf(`Affected releases are: -%s - -Do you really want to apply? - Helmfile will apply all your changes, as shown above. - -`, strings.Join(names, "\n")) - interactive := c.Interactive() - if !interactive || interactive && r.askForConfirmation(msg) { - rs := []state.ReleaseSpec{} - for _, r := range releases { - rs = append(rs, *r) - } - for _, r := range releasesToBeDeleted { - rs = append(rs, *r) - } - - r.helm.SetExtraArgs(argparser.GetArgs(c.Args(), r.state)...) - - st.Releases = rs - syncOpts := &state.SyncOpts{ - Set: c.Set(), - } - return st.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), syncOpts) - } - } - } - - affectedReleases.DisplayAffectedReleases(c.Logger()) - return fatalErrs -} - func (r *Run) Diff(c DiffConfigProvider) []error { st := r.state helm := r.helm diff --git a/pkg/app/two_pass_renderer.go b/pkg/app/two_pass_renderer.go index 17ab39ae..d9710ba2 100644 --- a/pkg/app/two_pass_renderer.go +++ b/pkg/app/two_pass_renderer.go @@ -31,12 +31,15 @@ func (r *desiredStateLoader) renderPrestate(firstPassEnv *environment.Environmen yamlBuf, err := firstPassRenderer.RenderTemplateContentToBuffer(content) if err != nil && r.logger != nil { r.logger.Debugf("first-pass rendering input of \"%s\":\n%s", filename, prependLineNumbers(string(content))) + r.logger.Debugf("template syntax error: %v", err) if yamlBuf == nil { // we have a template syntax error, let the second parse report - r.logger.Debugf("template syntax error: %v", err) return firstPassEnv, nil } } yamlData := yamlBuf.String() + if r.logger != nil { + r.logger.Debugf("first-pass rendering output of \"%s\":\n%s", filename, prependLineNumbers(yamlData)) + } // Work-around for https://github.com/golang/go/issues/24963 sanitized := strings.ReplaceAll(yamlData, "", "") diff --git a/pkg/exectest/helm.go b/pkg/exectest/helm.go new file mode 100644 index 00000000..ebcf1fa2 --- /dev/null +++ b/pkg/exectest/helm.go @@ -0,0 +1,167 @@ +package exectest + +import ( + "errors" + "fmt" + "strings" + "sync" + + "github.com/roboll/helmfile/pkg/helmexec" +) + +type ListKey struct { + Filter string + Flags string +} + +type DiffKey struct { + Name string + Chart string + Flags string +} + +type Helm struct { + Charts []string + Repo []string + Releases []Release + Deleted []Release + Lists map[ListKey]string + Diffs map[DiffKey]error + Diffed []Release + FailOnUnexpectedDiff bool + FailOnUnexpectedList bool + + UpdateDepsCallbacks map[string]func(string) error + + DiffMutex *sync.Mutex + ChartsMutex *sync.Mutex + ReleasesMutex *sync.Mutex +} + +type Release struct { + Name string + Flags []string +} + +type Affected struct { + Upgraded []*Release + Deleted []*Release + Failed []*Release +} + +func (helm *Helm) UpdateDeps(chart string) error { + if strings.Contains(chart, "error") { + return fmt.Errorf("simulated UpdateDeps failure for chart: %s", chart) + } + helm.Charts = append(helm.Charts, chart) + + if helm.UpdateDepsCallbacks != nil { + callback, exists := helm.UpdateDepsCallbacks[chart] + if exists { + if err := callback(chart); err != nil { + return err + } + } + } + return nil +} + +func (helm *Helm) BuildDeps(name, chart string) error { + if strings.Contains(chart, "error") { + return errors.New("error") + } + helm.Charts = append(helm.Charts, chart) + return nil +} + +func (helm *Helm) SetExtraArgs(args ...string) { + return +} +func (helm *Helm) SetHelmBinary(bin string) { + return +} +func (helm *Helm) AddRepo(name, repository, cafile, certfile, keyfile, username, password string) error { + helm.Repo = []string{name, repository, cafile, certfile, keyfile, username, password} + return nil +} +func (helm *Helm) UpdateRepo() error { + return nil +} +func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart string, flags ...string) error { + if strings.Contains(name, "error") { + return errors.New("error") + } + helm.sync(helm.ReleasesMutex, func() { + helm.Releases = append(helm.Releases, Release{Name: name, Flags: flags}) + }) + helm.sync(helm.ChartsMutex, func() { + helm.Charts = append(helm.Charts, chart) + }) + + return nil +} +func (helm *Helm) DiffRelease(context helmexec.HelmContext, name, chart string, flags ...string) error { + if helm.DiffMutex != nil { + helm.DiffMutex.Lock() + } + helm.Diffed = append(helm.Diffed, Release{Name: name, Flags: flags}) + if helm.DiffMutex != nil { + helm.DiffMutex.Unlock() + } + key := DiffKey{Name: name, Chart: chart, Flags: strings.Join(flags, "")} + err, ok := helm.Diffs[key] + if !ok && helm.FailOnUnexpectedDiff { + return fmt.Errorf("unexpected diff with key: %v", key) + } + return err +} +func (helm *Helm) ReleaseStatus(context helmexec.HelmContext, release string, flags ...string) error { + if strings.Contains(release, "error") { + return errors.New("error") + } + helm.Releases = append(helm.Releases, Release{Name: release, Flags: flags}) + return nil +} +func (helm *Helm) DeleteRelease(context helmexec.HelmContext, name string, flags ...string) error { + if strings.Contains(name, "error") { + return errors.New("error") + } + helm.Deleted = append(helm.Deleted, Release{Name: name, Flags: flags}) + return nil +} +func (helm *Helm) List(context helmexec.HelmContext, filter string, flags ...string) (string, error) { + key := ListKey{Filter: filter, Flags: strings.Join(flags, "")} + res, ok := helm.Lists[key] + if !ok && helm.FailOnUnexpectedList { + return "", fmt.Errorf("unexpected list key: %v", key) + } + return res, nil +} +func (helm *Helm) DecryptSecret(context helmexec.HelmContext, name string, flags ...string) (string, error) { + return "", nil +} +func (helm *Helm) TestRelease(context helmexec.HelmContext, name string, flags ...string) error { + if strings.Contains(name, "error") { + return errors.New("error") + } + helm.Releases = append(helm.Releases, Release{Name: name, Flags: flags}) + return nil +} +func (helm *Helm) Fetch(chart string, flags ...string) error { + return nil +} +func (helm *Helm) Lint(name, chart string, flags ...string) error { + return nil +} +func (helm *Helm) TemplateRelease(name, chart string, flags ...string) error { + return nil +} + +func (helm *Helm) sync(m *sync.Mutex, f func()) { + if m != nil { + m.Lock() + defer m.Unlock() + } + + f() +} diff --git a/pkg/helmexec/exit_error.go b/pkg/helmexec/exit_error.go index fd01132e..6b11638e 100644 --- a/pkg/helmexec/exit_error.go +++ b/pkg/helmexec/exit_error.go @@ -8,8 +8,8 @@ import ( func newExitError(helmCmdPath string, exitStatus int, errorMessage string) ExitError { return ExitError{ - msg: fmt.Sprintf("%s exited with status %d:\n%s", filepath.Base(helmCmdPath), exitStatus, indent(strings.TrimSpace(errorMessage))), - exitStatus: exitStatus, + Message: fmt.Sprintf("%s exited with status %d:\n%s", filepath.Base(helmCmdPath), exitStatus, indent(strings.TrimSpace(errorMessage))), + Code: exitStatus, } } @@ -23,14 +23,14 @@ func indent(text string) string { // ExitError is created whenever your shell command exits with a non-zero exit status type ExitError struct { - msg string - exitStatus int + Message string + Code int } func (e ExitError) Error() string { - return e.msg + return e.Message } func (e ExitError) ExitStatus() int { - return e.exitStatus + return e.Code } diff --git a/pkg/state/state.go b/pkg/state/state.go index 29b9194e..075f9c2f 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -10,6 +10,7 @@ import ( "os" "path" "path/filepath" + "regexp" "sort" "strconv" "strings" @@ -20,9 +21,6 @@ import ( "github.com/roboll/helmfile/pkg/helmexec" "github.com/roboll/helmfile/pkg/remote" "github.com/roboll/helmfile/pkg/tmpl" - "github.com/variantdev/dag/pkg/dag" - - "regexp" "github.com/tatsushid/go-prettytable" "github.com/variantdev/vals" @@ -45,7 +43,7 @@ type HelmState struct { Helmfiles []SubHelmfileSpec `yaml:"helmfiles,omitempty"` DeprecatedContext string `yaml:"context,omitempty"` DeprecatedReleases []ReleaseSpec `yaml:"charts,omitempty"` - Namespace string `yaml:"namespace,omitempty"` + OverrideNamespace string `yaml:"namespace,omitempty"` Repositories []RepositorySpec `yaml:"repositories,omitempty"` Releases []ReleaseSpec `yaml:"releases,omitempty"` Selectors []string `yaml:"-"` @@ -195,6 +193,12 @@ type ReleaseSpec struct { installedVersion string } +type Release struct { + ReleaseSpec + + Filtered bool +} + // SetValue are the key values to set on a helm release type SetValue struct { Name string `yaml:"name,omitempty"` @@ -217,9 +221,16 @@ const MissingFileHandlerInfo = "Info" const MissingFileHandlerWarn = "Warn" const MissingFileHandlerDebug = "Debug" -func (st *HelmState) applyDefaultsTo(spec *ReleaseSpec) { - if st.Namespace != "" { - spec.Namespace = st.Namespace +func (st *HelmState) ApplyOverrides(spec *ReleaseSpec) { + if st.OverrideNamespace != "" { + spec.Namespace = st.OverrideNamespace + + for i := 0; i < len(spec.Needs); i++ { + n := spec.Needs[i] + if len(strings.Split(n, "/")) == 1 { + spec.Needs[i] = st.OverrideNamespace + "/" + n + } + } } } @@ -290,7 +301,7 @@ func (st *HelmState) prepareSyncReleases(helm helmexec.Interface, additionalValu }, func(workerIndex int) { for release := range jobs { - st.applyDefaultsTo(release) + st.ApplyOverrides(release) // If `installed: false`, the only potential operation on this release would be uninstalling. // We skip generating values files in that case, because for an uninstall with `helm delete`, we don't need to those. @@ -370,10 +381,10 @@ func (st *HelmState) isReleaseInstalled(context helmexec.HelmContext, helm helme return false, nil } -func (st *HelmState) DetectReleasesToBeDeleted(helm helmexec.Interface) ([]*ReleaseSpec, error) { - detected := []*ReleaseSpec{} - for i := range st.Releases { - release := st.Releases[i] +func (st *HelmState) DetectReleasesToBeDeleted(helm helmexec.Interface, releases []ReleaseSpec) ([]ReleaseSpec, error) { + detected := []ReleaseSpec{} + for i := range releases { + release := releases[i] if !release.Desired() { installed, err := st.isReleaseInstalled(st.createHelmContext(&release, 0), helm, release) @@ -382,7 +393,7 @@ func (st *HelmState) DetectReleasesToBeDeleted(helm helmexec.Interface) ([]*Rele } else if installed { // Otherwise `release` messed up(https://github.com/roboll/helmfile/issues/554) r := release - detected = append(detected, &r) + detected = append(detected, r) } } } @@ -399,76 +410,7 @@ func (o *SyncOpts) Apply(opts *SyncOpts) { *opts = *o } -// SyncReleases wrapper for executing helm upgrade on the releases -func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helmexec.Interface, additionalValues []string, workerLimit int, opt ...SyncOpt) []error { - opts := &SyncOpts{} - for _, o := range opt { - o.Apply(opts) - } - - preps, prepErrs := st.prepareSyncReleases(helm, additionalValues, workerLimit, opts) - if len(prepErrs) > 0 { - 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, workerLimit, prepsInGroup) - if len(errs) > 0 { - return errs - } - } - - return nil -} - -func releaseToID(r *ReleaseSpec) string { +func ReleaseToID(r *ReleaseSpec) string { var id string tns := r.TillerNamespace @@ -486,18 +428,112 @@ func releaseToID(r *ReleaseSpec) string { return id } -func (st *HelmState) syncReleaseGroup(affectedReleases *AffectedReleases, helm helmexec.Interface, concurrency int, preps []syncPrepareResult) []error { +// DeleteReleasesForSync deletes releases that are marked for deletion +func (st *HelmState) DeleteReleasesForSync(affectedReleases *AffectedReleases, helm helmexec.Interface, workerLimit int) []error { errs := []error{} - jobQueue := make(chan *syncPrepareResult, len(preps)) - results := make(chan syncResult, len(preps)) - if concurrency == 0 { - concurrency = len(preps) + + releases := st.Releases + + jobQueue := make(chan *ReleaseSpec, len(releases)) + results := make(chan syncResult, len(releases)) + if workerLimit == 0 { + workerLimit = len(releases) } m := new(sync.Mutex) st.scatterGather( - concurrency, + workerLimit, + len(releases), + func() { + for i := 0; i < len(releases); i++ { + jobQueue <- &releases[i] + } + close(jobQueue) + }, + func(workerIndex int) { + for release := range jobQueue { + var relErr *ReleaseError + context := st.createHelmContext(release, workerIndex) + + if _, err := st.triggerPresyncEvent(release, "sync"); err != nil { + relErr = newReleaseError(release, err) + } else { + var args []string + if isHelm3() { + args = []string{} + } else { + args = []string{"--purge"} + } + deletionFlags := st.appendConnectionFlags(args, release) + m.Lock() + if err := helm.DeleteRelease(context, release.Name, deletionFlags...); err != nil { + affectedReleases.Failed = append(affectedReleases.Failed, release) + relErr = newReleaseError(release, err) + } else { + affectedReleases.Deleted = append(affectedReleases.Deleted, release) + } + m.Unlock() + } + + if relErr == nil { + results <- syncResult{} + } else { + results <- syncResult{errors: []*ReleaseError{relErr}} + } + + if _, err := st.triggerPostsyncEvent(release, relErr, "sync"); err != nil { + st.logger.Warnf("warn: %v\n", err) + } + + if _, err := st.triggerCleanupEvent(release, "sync"); err != nil { + st.logger.Warnf("warn: %v\n", err) + } + } + }, + func() { + for i := 0; i < len(releases); { + select { + case res := <-results: + if len(res.errors) > 0 { + for _, e := range res.errors { + errs = append(errs, e) + } + } + } + i++ + } + }, + ) + if len(errs) > 0 { + return errs + } + return nil +} + +// SyncReleases wrapper for executing helm upgrade on the releases +func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helmexec.Interface, additionalValues []string, workerLimit int, opt ...SyncOpt) []error { + opts := &SyncOpts{} + for _, o := range opt { + o.Apply(opts) + } + + preps, prepErrs := st.prepareSyncReleases(helm, additionalValues, workerLimit, opts) + if len(prepErrs) > 0 { + return prepErrs + } + + errs := []error{} + jobQueue := make(chan *syncPrepareResult, len(preps)) + results := make(chan syncResult, len(preps)) + if workerLimit == 0 { + workerLimit = len(preps) + } + + m := new(sync.Mutex) + + st.scatterGather( + workerLimit, len(preps), func() { for i := 0; i < len(preps); i++ { @@ -730,7 +766,7 @@ func (st *HelmState) TemplateReleases(helm helmexec.Interface, outputDir string, continue } - st.applyDefaultsTo(&release) + st.ApplyOverrides(&release) flags, err := st.flagsForTemplate(helm, &release, 0) if err != nil { @@ -916,7 +952,7 @@ func (st *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValu for release := range jobs { errs := []error{} - st.applyDefaultsTo(release) + st.ApplyOverrides(release) // TODO We need a long-term fix for this :) // See https://github.com/roboll/helmfile/issues/737 @@ -1020,7 +1056,7 @@ type DiffOpt interface{ Apply(*DiffOpts) } // DiffReleases wrapper for executing helm diff on the releases // It returns releases that had any changes -func (st *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, detailedExitCode, suppressSecrets bool, triggerCleanupEvents bool, opt ...DiffOpt) ([]*ReleaseSpec, []error) { +func (st *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []string, workerLimit int, detailedExitCode, suppressSecrets bool, triggerCleanupEvents bool, opt ...DiffOpt) ([]ReleaseSpec, []error) { opts := &DiffOpts{} for _, o := range opt { o.Apply(opts) @@ -1028,13 +1064,13 @@ func (st *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []st preps, prepErrs := st.prepareDiffReleases(helm, additionalValues, workerLimit, detailedExitCode, suppressSecrets, opts) if len(prepErrs) > 0 { - return []*ReleaseSpec{}, prepErrs + return []ReleaseSpec{}, prepErrs } jobQueue := make(chan *diffPrepareResult, len(preps)) results := make(chan diffResult, len(preps)) - rs := []*ReleaseSpec{} + rs := []ReleaseSpec{} errs := []error{} st.scatterGather( @@ -1076,7 +1112,7 @@ func (st *HelmState) DiffReleases(helm helmexec.Interface, additionalValues []st if res.err != nil { errs = append(errs, res.err) if res.err.Code == 2 { - rs = append(rs, res.err.ReleaseSpec) + rs = append(rs, *res.err.ReleaseSpec) } } } @@ -1102,7 +1138,7 @@ func (st *HelmState) ReleaseStatuses(helm helmexec.Interface, workerLimit int) [ // 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 { - return st.dagAwareReverseIterateOnReleases(helm, concurrency, func(release ReleaseSpec, workerIndex int) error { + return st.scatterGatherReleases(helm, concurrency, func(release ReleaseSpec, workerIndex int) error { if !release.Desired() { return nil } @@ -1180,19 +1216,17 @@ func (st *HelmState) Clean() []error { return nil } -// FilterReleases allows for the execution of helm commands against a subset of the releases in the helmfile. -func (st *HelmState) FilterReleases() error { - var filteredReleases []ReleaseSpec - releaseSet := map[string][]ReleaseSpec{} +func MarkFilteredReleases(releases []ReleaseSpec, selectors []string) ([]Release, error) { + var filteredReleases []Release filters := []ReleaseFilter{} - for _, label := range st.Selectors { + for _, label := range selectors { f, err := ParseLabels(label) if err != nil { - return err + return nil, err } filters = append(filters, f) } - for _, r := range st.Releases { + for _, r := range releases { if r.Labels == nil { r.Labels = map[string]string{} } @@ -1202,22 +1236,40 @@ func (st *HelmState) FilterReleases() error { // Strip off just the last portion for the name stable/newrelic would give newrelic chartSplit := strings.Split(r.Chart, "/") r.Labels["chart"] = chartSplit[len(chartSplit)-1] + var matched bool for _, f := range filters { if r.Labels == nil { r.Labels = map[string]string{} } if f.Match(r) { - releaseSet[r.Name] = append(releaseSet[r.Name], r) - continue + matched = true + break } } + res := Release{ + ReleaseSpec: r, + Filtered: len(filters) > 0 && !matched, + } + filteredReleases = append(filteredReleases, res) } - for _, r := range releaseSet { - filteredReleases = append(filteredReleases, r...) + + return filteredReleases, nil +} + +// FilterReleases allows for the execution of helm commands against a subset of the releases in the helmfile. +func (st *HelmState) FilterReleases() error { + filteredReleases, err := MarkFilteredReleases(st.Releases, st.Selectors) + if err != nil { + return err } - st.Releases = filteredReleases - numFound := len(filteredReleases) - st.logger.Debugf("%d release(s) matching %s found in %s\n", numFound, strings.Join(st.Selectors, ","), st.FilePath) + var releases []ReleaseSpec + for _, r := range filteredReleases { + if !r.Filtered { + releases = append(releases, r.ReleaseSpec) + } + } + st.Releases = releases + st.logger.Debugf("%d release(s) matching %s found in %s\n", len(filteredReleases), strings.Join(st.Selectors, ","), st.FilePath) return nil } @@ -1267,7 +1319,7 @@ func (st *HelmState) triggerReleaseEvent(evt string, evtErr error, r *ReleaseSpe Hooks: r.Hooks, StateFilePath: st.FilePath, BasePath: st.basePath, - Namespace: st.Namespace, + Namespace: st.OverrideNamespace, Env: st.Env, Logger: st.logger, ReadFile: st.readFile, diff --git a/pkg/state/state_exec_tmpl.go b/pkg/state/state_exec_tmpl.go index 1d570856..c68ffbc2 100644 --- a/pkg/state/state_exec_tmpl.go +++ b/pkg/state/state_exec_tmpl.go @@ -39,7 +39,7 @@ func (st *HelmState) mustLoadVals() map[string]interface{} { func (st *HelmState) valuesFileTemplateData() EnvironmentTemplateData { return EnvironmentTemplateData{ Environment: st.Env, - Namespace: st.Namespace, + Namespace: st.OverrideNamespace, Values: st.mustLoadVals(), } } diff --git a/pkg/state/state_exec_tmpl_test.go b/pkg/state/state_exec_tmpl_test.go index 05aa00fe..7ba7acac 100644 --- a/pkg/state/state_exec_tmpl_test.go +++ b/pkg/state/state_exec_tmpl_test.go @@ -138,9 +138,9 @@ func TestHelmState_executeTemplates(t *testing.T) { HelmDefaults: HelmSpec{ KubeContext: "test_context", }, - Env: environment.Environment{Name: "test_env"}, - Namespace: "test-namespace_", - Repositories: nil, + Env: environment.Environment{Name: "test_env"}, + OverrideNamespace: "test-namespace_", + Repositories: nil, Releases: []ReleaseSpec{ tt.input, }, @@ -238,9 +238,9 @@ func TestHelmState_recursiveRefsTemplates(t *testing.T) { HelmDefaults: HelmSpec{ KubeContext: "test_context", }, - Env: environment.Environment{Name: "test_env"}, - Namespace: "test-namespace_", - Repositories: nil, + Env: environment.Environment{Name: "test_env"}, + OverrideNamespace: "test-namespace_", + Repositories: nil, Releases: []ReleaseSpec{ tt.input, }, diff --git a/pkg/state/state_run.go b/pkg/state/state_run.go index 9cbea83f..9c7a2f53 100644 --- a/pkg/state/state_run.go +++ b/pkg/state/state_run.go @@ -2,7 +2,7 @@ package state import ( "fmt" - "strings" + "sort" "sync" "github.com/roboll/helmfile/pkg/helmexec" @@ -104,51 +104,96 @@ func (st *HelmState) iterateOnReleases(helm helmexec.Interface, concurrency int, return nil } -func (st *HelmState) dagAwareReverseIterateOnReleases(helm helmexec.Interface, concurrency int, - do func(ReleaseSpec, int) error) []error { +type PlanOpts struct { + Reverse bool +} - idToRelease := map[string]ReleaseSpec{} +func PlanReleases(releases []ReleaseSpec, selectors []string, reverse bool) ([][]Release, error) { + marked, err := MarkFilteredReleases(releases, selectors) + if err != nil { + return nil, err + } - preps := st.Releases + groups, err := SortedReleaseGroups(marked, reverse) + if err != nil { + return nil, err + } + + return groups, nil +} + +func SortedReleaseGroups(releases []Release, reverse bool) ([][]Release, error) { + groups, err := GroupReleasesByDependency(releases) + if err != nil { + return nil, err + } + + if reverse { + sort.Slice(groups, func(i, j int) bool { + return j < i + }) + } + + return groups, nil +} + +func GroupReleasesByDependency(releases []Release) ([][]Release, error) { + idToReleases := map[string][]Release{} d := dag.New() - for _, r := range preps { + for _, r := range releases { - id := releaseToID(&r) + id := ReleaseToID(&r.ReleaseSpec) - idToRelease[id] = r + idToReleases[id] = append(idToReleases[id], r) - d.Add(id, dag.Dependencies(r.Needs)) + // Only compute dependencies from non-filtered releases + if !r.Filtered { + d.Add(id, dag.Dependencies(r.Needs)) + } + } + + for _, r := range releases { + if !r.Filtered { + for _, n := range r.Needs { + if _, ok := idToReleases[n]; !ok { + id := ReleaseToID(&r.ReleaseSpec) + return nil, fmt.Errorf("%q has dependency to inexistent release %q", id, n) + } + } + } } plan, err := d.Plan() if err != nil { - return []error{err} + return nil, err } - groupsTotal := len(plan) + var result [][]Release - st.logger.Debugf("processing %d groups of releases in this order: %s", groupsTotal, plan) - - for groupIndex := len(plan) - 1; groupIndex >= 0; groupIndex-- { + for groupIndex := 0; groupIndex < len(plan); groupIndex++ { dagNodesInGroup := plan[groupIndex] var idsInGroup []string - var releasesInGroup []ReleaseSpec + var releasesInGroup []Release 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, ", ")) + // Make the helmfile behavior deterministic for reproducibility and ease of testing + sort.Strings(idsInGroup) - errs := st.iterateOnReleases(helm, concurrency, releasesInGroup, do) - - if len(errs) > 0 { - return errs + for _, id := range idsInGroup { + releases, ok := idToReleases[id] + if !ok { + panic(fmt.Errorf("bug: unexpectedly failed to get releases for id %q", id)) + } + releasesInGroup = append(releasesInGroup, releases...) } + + result = append(result, releasesInGroup) } - return nil + return result, nil } diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 786acb03..02e35ddd 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -1,20 +1,17 @@ package state import ( + "fmt" "io/ioutil" "os" "path/filepath" "reflect" "testing" + "github.com/roboll/helmfile/pkg/exectest" "github.com/roboll/helmfile/pkg/helmexec" "github.com/roboll/helmfile/pkg/testhelper" "github.com/variantdev/vals" - - "errors" - "strings" - - "fmt" ) var logger = helmexec.NewLogger(os.Stdout, "warn") @@ -133,12 +130,12 @@ func TestHelmState_applyDefaultsTo(t *testing.T) { basePath: tt.fields.BaseChartPath, DeprecatedContext: tt.fields.Context, DeprecatedReleases: tt.fields.DeprecatedReleases, - Namespace: tt.fields.Namespace, + OverrideNamespace: tt.fields.Namespace, Repositories: tt.fields.Repositories, Releases: tt.fields.Releases, } - if state.applyDefaultsTo(&tt.args.spec); !reflect.DeepEqual(tt.args.spec, tt.want) { - t.Errorf("HelmState.applyDefaultsTo() = %v, want %v", tt.args.spec, tt.want) + if state.ApplyOverrides(&tt.args.spec); !reflect.DeepEqual(tt.args.spec, tt.want) { + t.Errorf("HelmState.ApplyOverrides() = %v, want %v", tt.args.spec, tt.want) } }) } @@ -665,124 +662,11 @@ func Test_normalizeChart(t *testing.T) { // mocking helmexec.Interface -type listKey struct { - filter string - flags string -} - -type mockHelmExec struct { - charts []string - repo []string - releases []mockRelease - deleted []mockRelease - lists map[listKey]string - diffed []mockRelease - - updateDepsCallbacks map[string]func(string) error -} - -type mockRelease struct { - name string - flags []string -} - -type mockAffected struct { - upgraded []*mockRelease - deleted []*mockRelease - failed []*mockRelease -} - -func (helm *mockHelmExec) UpdateDeps(chart string) error { - if strings.Contains(chart, "error") { - return fmt.Errorf("simulated UpdateDeps failure for chart: %s", chart) - } - helm.charts = append(helm.charts, chart) - - if helm.updateDepsCallbacks != nil { - callback, exists := helm.updateDepsCallbacks[chart] - if exists { - if err := callback(chart); err != nil { - return err - } - } - } - return nil -} - -func (helm *mockHelmExec) BuildDeps(name, chart string) error { - if strings.Contains(chart, "error") { - return errors.New("error") - } - helm.charts = append(helm.charts, chart) - return nil -} - -func (helm *mockHelmExec) SetExtraArgs(args ...string) { - return -} -func (helm *mockHelmExec) SetHelmBinary(bin string) { - return -} -func (helm *mockHelmExec) AddRepo(name, repository, cafile, certfile, keyfile, username, password string) error { - helm.repo = []string{name, repository, cafile, certfile, keyfile, username, password} - return nil -} -func (helm *mockHelmExec) UpdateRepo() error { - return nil -} -func (helm *mockHelmExec) SyncRelease(context helmexec.HelmContext, name, chart string, flags ...string) error { - if strings.Contains(name, "error") { - return errors.New("error") - } - helm.releases = append(helm.releases, mockRelease{name: name, flags: flags}) - helm.charts = append(helm.charts, chart) - return nil -} -func (helm *mockHelmExec) DiffRelease(context helmexec.HelmContext, name, chart string, flags ...string) error { - helm.diffed = append(helm.diffed, mockRelease{name: name, flags: flags}) - return nil -} -func (helm *mockHelmExec) ReleaseStatus(context helmexec.HelmContext, release string, flags ...string) error { - if strings.Contains(release, "error") { - return errors.New("error") - } - helm.releases = append(helm.releases, mockRelease{name: release, flags: flags}) - return nil -} -func (helm *mockHelmExec) DeleteRelease(context helmexec.HelmContext, name string, flags ...string) error { - if strings.Contains(name, "error") { - return errors.New("error") - } - helm.deleted = append(helm.deleted, mockRelease{name: name, flags: flags}) - return nil -} -func (helm *mockHelmExec) List(context helmexec.HelmContext, filter string, flags ...string) (string, error) { - return helm.lists[listKey{filter: filter, flags: strings.Join(flags, "")}], nil -} -func (helm *mockHelmExec) DecryptSecret(context helmexec.HelmContext, name string, flags ...string) (string, error) { - return "", nil -} -func (helm *mockHelmExec) TestRelease(context helmexec.HelmContext, name string, flags ...string) error { - if strings.Contains(name, "error") { - return errors.New("error") - } - helm.releases = append(helm.releases, mockRelease{name: name, flags: flags}) - return nil -} -func (helm *mockHelmExec) Fetch(chart string, flags ...string) error { - return nil -} -func (helm *mockHelmExec) Lint(name, chart string, flags ...string) error { - return nil -} -func (helm *mockHelmExec) TemplateRelease(name, chart string, flags ...string) error { - return nil -} func TestHelmState_SyncRepos(t *testing.T) { tests := []struct { name string repos []RepositorySpec - helm *mockHelmExec + helm *exectest.Helm envs map[string]string want []string }{ @@ -798,7 +682,7 @@ func TestHelmState_SyncRepos(t *testing.T) { Password: "", }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, want: []string{"name", "http://example.com/", "", "", "", "", ""}, }, { @@ -813,7 +697,7 @@ func TestHelmState_SyncRepos(t *testing.T) { Password: "", }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, want: []string{"name", "http://example.com/", "", "certfile", "keyfile", "", ""}, }, { @@ -827,7 +711,7 @@ func TestHelmState_SyncRepos(t *testing.T) { Password: "", }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, want: []string{"name", "http://example.com/", "cafile", "", "", "", ""}, }, { @@ -842,7 +726,7 @@ func TestHelmState_SyncRepos(t *testing.T) { Password: "example_password", }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, want: []string{"name", "http://example.com/", "", "", "", "example_user", "example_password"}, }, } @@ -858,8 +742,8 @@ func TestHelmState_SyncRepos(t *testing.T) { state := &HelmState{ Repositories: tt.repos, } - if _ = state.SyncRepos(tt.helm); !reflect.DeepEqual(tt.helm.repo, tt.want) { - t.Errorf("HelmState.SyncRepos() for [%s] = %v, want %v", tt.name, tt.helm.repo, tt.want) + if _ = state.SyncRepos(tt.helm); !reflect.DeepEqual(tt.helm.Repo, tt.want) { + t.Errorf("HelmState.SyncRepos() for [%s] = %v, want %v", tt.name, tt.helm.Repo, tt.want) } }) } @@ -869,8 +753,8 @@ func TestHelmState_SyncReleases(t *testing.T) { tests := []struct { name string releases []ReleaseSpec - helm *mockHelmExec - wantReleases []mockRelease + helm *exectest.Helm + wantReleases []exectest.Release wantErrorMsgs []string }{ { @@ -881,8 +765,8 @@ func TestHelmState_SyncReleases(t *testing.T) { Chart: "foo", }, }, - helm: &mockHelmExec{}, - wantReleases: []mockRelease{{"releaseName", []string{}}}, + helm: &exectest.Helm{}, + wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{}}}, }, { name: "with tiller args", @@ -893,8 +777,8 @@ func TestHelmState_SyncReleases(t *testing.T) { TillerNamespace: "tillerns", }, }, - helm: &mockHelmExec{}, - wantReleases: []mockRelease{{"releaseName", []string{"--tiller-namespace", "tillerns"}}}, + helm: &exectest.Helm{}, + wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--tiller-namespace", "tillerns"}}}, }, { name: "escaped values", @@ -914,8 +798,8 @@ func TestHelmState_SyncReleases(t *testing.T) { }, }, }, - helm: &mockHelmExec{}, - wantReleases: []mockRelease{{"releaseName", []string{"--set", "someList=a\\,b\\,c", "--set", "json=\\{\"name\": \"john\"\\}"}}}, + helm: &exectest.Helm{}, + wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--set", "someList=a\\,b\\,c", "--set", "json=\\{\"name\": \"john\"\\}"}}}, }, { name: "set single value from file", @@ -939,8 +823,8 @@ func TestHelmState_SyncReleases(t *testing.T) { }, }, }, - helm: &mockHelmExec{}, - wantReleases: []mockRelease{{"releaseName", []string{"--set", "foo=FOO", "--set-file", "bar=path/to/bar", "--set", "baz=BAZ"}}}, + helm: &exectest.Helm{}, + wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--set", "foo=FOO", "--set-file", "bar=path/to/bar", "--set", "baz=BAZ"}}}, }, { name: "set single array value in an array", @@ -959,108 +843,8 @@ func TestHelmState_SyncReleases(t *testing.T) { }, }, }, - helm: &mockHelmExec{}, - 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`}, + helm: &exectest.Helm{}, + wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--set", "foo.bar[0]={A,B}"}}}, }, } for i := range tests { @@ -1087,8 +871,8 @@ func TestHelmState_SyncReleases(t *testing.T) { 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) + if !reflect.DeepEqual(tt.helm.Releases, tt.wantReleases) { + t.Errorf("HelmState.SyncReleases() for [%s] = %v, want %v", tt.name, tt.helm.Releases, tt.wantReleases) } }) } @@ -1177,11 +961,11 @@ func TestHelmState_SyncReleases_MissingValuesFileForUndesiredRelease(t *testing. } fs := testhelper.NewTestFs(map[string]string{}) state = injectFs(state, fs) - helm := &mockHelmExec{ - lists: map[listKey]string{}, + helm := &exectest.Helm{ + Lists: map[exectest.ListKey]string{}, } //simulate the helm.list call result - helm.lists[listKey{filter: "^" + tt.release.Name + "$"}] = tt.listResult + helm.Lists[exectest.ListKey{Filter: "^" + tt.release.Name + "$"}] = tt.listResult affectedReleases := AffectedReleases{} errs := state.SyncReleases(&affectedReleases, helm, []string{}, 1) @@ -1213,7 +997,7 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) { name string releases []ReleaseSpec installed []bool - wantAffected mockAffected + wantAffected exectest.Affected }{ { name: "2 release", @@ -1227,7 +1011,14 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) { Chart: "bar", }, }, - wantAffected: mockAffected{[]*mockRelease{{"releaseNameFoo", []string{}}, {"releaseNameBar", []string{}}}, nil, nil}, + wantAffected: exectest.Affected{ + Upgraded: []*exectest.Release{ + {Name: "releaseNameFoo", Flags: []string{}}, + {Name: "releaseNameBar", Flags: []string{}}, + }, + Deleted: nil, + Failed: nil, + }, }, { name: "2 removed", @@ -1243,8 +1034,15 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) { Installed: &no, }, }, - installed: []bool{true, true}, - wantAffected: mockAffected{nil, []*mockRelease{{"releaseNameFoo", []string{}}, {"releaseNameBar", []string{}}}, nil}, + installed: []bool{true, true}, + wantAffected: exectest.Affected{ + Upgraded: nil, + Deleted: []*exectest.Release{ + {Name: "releaseNameFoo", Flags: []string{}}, + {Name: "releaseNameBar", Flags: []string{}}, + }, + Failed: nil, + }, }, { name: "2 errors", @@ -1258,7 +1056,14 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) { Chart: "foo", }, }, - wantAffected: mockAffected{nil, nil, []*mockRelease{{"releaseNameFoo-error", []string{}}, {"releaseNameBar-error", []string{}}}}, + wantAffected: exectest.Affected{ + Upgraded: nil, + Deleted: nil, + Failed: []*exectest.Release{ + {Name: "releaseNameFoo-error", Flags: []string{}}, + {Name: "releaseNameBar-error", Flags: []string{}}, + }, + }, }, { name: "1 removed, 1 new, 1 error", @@ -1277,8 +1082,18 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) { Chart: "foo", }, }, - installed: []bool{true, true, true}, - wantAffected: mockAffected{[]*mockRelease{{"releaseNameFoo", []string{}}}, []*mockRelease{{"releaseNameBar", []string{}}}, []*mockRelease{{"releaseNameFoo-error", []string{}}}}, + installed: []bool{true, true, true}, + wantAffected: exectest.Affected{ + Upgraded: []*exectest.Release{ + {Name: "releaseNameFoo", Flags: []string{}}, + }, + Deleted: []*exectest.Release{ + {Name: "releaseNameBar", Flags: []string{}}, + }, + Failed: []*exectest.Release{ + {Name: "releaseNameFoo-error", Flags: []string{}}, + }, + }, }, } for i := range tests { @@ -1289,33 +1104,33 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) { logger: logger, valsRuntime: valsRuntime, } - helm := &mockHelmExec{ - lists: map[listKey]string{}, + helm := &exectest.Helm{ + Lists: map[exectest.ListKey]string{}, } //simulate the release is already installed for i, release := range tt.releases { if tt.installed != nil && tt.installed[i] { - helm.lists[listKey{filter: "^" + release.Name + "$"}] = release.Name + helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$"}] = release.Name } } affectedReleases := AffectedReleases{} if err := state.SyncReleases(&affectedReleases, helm, []string{}, 1); err != nil { - if !testEq(affectedReleases.Failed, tt.wantAffected.failed) { - t.Errorf("HelmState.SynchAffectedRelease() error failed for [%s] = %v, want %v", tt.name, affectedReleases.Failed, tt.wantAffected.failed) + if !testEq(affectedReleases.Failed, tt.wantAffected.Failed) { + t.Errorf("HelmState.SynchAffectedRelease() error failed for [%s] = %v, want %v", tt.name, affectedReleases.Failed, tt.wantAffected.Failed) } //else expected error } - if !testEq(affectedReleases.Upgraded, tt.wantAffected.upgraded) { - t.Errorf("HelmState.SynchAffectedRelease() upgrade failed for [%s] = %v, want %v", tt.name, affectedReleases.Upgraded, tt.wantAffected.upgraded) + if !testEq(affectedReleases.Upgraded, tt.wantAffected.Upgraded) { + t.Errorf("HelmState.SynchAffectedRelease() upgrade failed for [%s] = %v, want %v", tt.name, affectedReleases.Upgraded, tt.wantAffected.Upgraded) } - if !testEq(affectedReleases.Deleted, tt.wantAffected.deleted) { - t.Errorf("HelmState.SynchAffectedRelease() deleted failed for [%s] = %v, want %v", tt.name, affectedReleases.Deleted, tt.wantAffected.deleted) + if !testEq(affectedReleases.Deleted, tt.wantAffected.Deleted) { + t.Errorf("HelmState.SynchAffectedRelease() deleted failed for [%s] = %v, want %v", tt.name, affectedReleases.Deleted, tt.wantAffected.Deleted) } }) } } -func testEq(a []*ReleaseSpec, b []*mockRelease) bool { +func testEq(a []*ReleaseSpec, b []*exectest.Release) bool { // If one is nil, the other must also be nil. if (a == nil) != (b == nil) { @@ -1327,7 +1142,7 @@ func testEq(a []*ReleaseSpec, b []*mockRelease) bool { } for i := range a { - if a[i].Name != b[i].name { + if a[i].Name != b[i].Name { return false } } @@ -1391,11 +1206,11 @@ func TestGetDeployedVersion(t *testing.T) { logger: logger, valsRuntime: valsRuntime, } - helm := &mockHelmExec{ - lists: map[listKey]string{}, + helm := &exectest.Helm{ + Lists: map[exectest.ListKey]string{}, } //simulate the helm.list call result - helm.lists[listKey{filter: "^" + tt.release.Name + "$"}] = tt.listResult + helm.Lists[exectest.ListKey{Filter: "^" + tt.release.Name + "$"}] = tt.listResult affectedReleases := AffectedReleases{} state.SyncReleases(&affectedReleases, helm, []string{}, 1) @@ -1411,8 +1226,8 @@ func TestHelmState_DiffReleases(t *testing.T) { tests := []struct { name string releases []ReleaseSpec - helm *mockHelmExec - wantReleases []mockRelease + helm *exectest.Helm + wantReleases []exectest.Release }{ { name: "normal release", @@ -1422,8 +1237,8 @@ func TestHelmState_DiffReleases(t *testing.T) { Chart: "foo", }, }, - helm: &mockHelmExec{}, - wantReleases: []mockRelease{{"releaseName", []string{}}}, + helm: &exectest.Helm{}, + wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{}}}, }, { name: "with tiller args", @@ -1434,8 +1249,8 @@ func TestHelmState_DiffReleases(t *testing.T) { TillerNamespace: "tillerns", }, }, - helm: &mockHelmExec{}, - wantReleases: []mockRelease{{"releaseName", []string{"--tiller-namespace", "tillerns"}}}, + helm: &exectest.Helm{}, + wantReleases: []exectest.Release{{Name: "releaseName", Flags: []string{"--tiller-namespace", "tillerns"}}}, }, { name: "escaped values", @@ -1455,8 +1270,10 @@ func TestHelmState_DiffReleases(t *testing.T) { }, }, }, - helm: &mockHelmExec{}, - wantReleases: []mockRelease{{"releaseName", []string{"--set", "someList=a\\,b\\,c", "--set", "json=\\{\"name\": \"john\"\\}"}}}, + helm: &exectest.Helm{}, + wantReleases: []exectest.Release{ + {Name: "releaseName", Flags: []string{"--set", "someList=a\\,b\\,c", "--set", "json=\\{\"name\": \"john\"\\}"}}, + }, }, { name: "set single value from file", @@ -1480,8 +1297,10 @@ func TestHelmState_DiffReleases(t *testing.T) { }, }, }, - helm: &mockHelmExec{}, - wantReleases: []mockRelease{{"releaseName", []string{"--set", "foo=FOO", "--set-file", "bar=path/to/bar", "--set", "baz=BAZ"}}}, + helm: &exectest.Helm{}, + wantReleases: []exectest.Release{ + {Name: "releaseName", Flags: []string{"--set", "foo=FOO", "--set-file", "bar=path/to/bar", "--set", "baz=BAZ"}}, + }, }, { name: "set single array value in an array", @@ -1500,8 +1319,10 @@ func TestHelmState_DiffReleases(t *testing.T) { }, }, }, - helm: &mockHelmExec{}, - wantReleases: []mockRelease{{"releaseName", []string{"--set", "foo.bar[0]={A,B}"}}}, + helm: &exectest.Helm{}, + wantReleases: []exectest.Release{ + {Name: "releaseName", Flags: []string{"--set", "foo.bar[0]={A,B}"}}, + }, }, } for i := range tests { @@ -1516,8 +1337,8 @@ func TestHelmState_DiffReleases(t *testing.T) { if errs != nil && len(errs) > 0 { t.Errorf("unexpected error: %v", errs) } - if !reflect.DeepEqual(tt.helm.diffed, tt.wantReleases) { - t.Errorf("HelmState.DiffReleases() for [%s] = %v, want %v", tt.name, tt.helm.releases, tt.wantReleases) + if !reflect.DeepEqual(tt.helm.Diffed, tt.wantReleases) { + t.Errorf("HelmState.DiffReleases() for [%s] = %v, want %v", tt.name, tt.helm.Releases, tt.wantReleases) } }) } @@ -1527,7 +1348,7 @@ func TestHelmState_SyncReleasesCleanup(t *testing.T) { tests := []struct { name string releases []ReleaseSpec - helm *mockHelmExec + helm *exectest.Helm expectedNumRemovedFiles int }{ { @@ -1538,7 +1359,7 @@ func TestHelmState_SyncReleasesCleanup(t *testing.T) { Chart: "foo", }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, expectedNumRemovedFiles: 0, }, { @@ -1554,7 +1375,7 @@ func TestHelmState_SyncReleasesCleanup(t *testing.T) { }, }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, expectedNumRemovedFiles: 1, }, { @@ -1571,7 +1392,7 @@ func TestHelmState_SyncReleasesCleanup(t *testing.T) { }, }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, expectedNumRemovedFiles: 2, }, } @@ -1611,7 +1432,7 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) { tests := []struct { name string releases []ReleaseSpec - helm *mockHelmExec + helm *exectest.Helm expectedNumRemovedFiles int }{ { @@ -1622,7 +1443,7 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) { Chart: "foo", }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, expectedNumRemovedFiles: 0, }, { @@ -1638,7 +1459,7 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) { }, }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, expectedNumRemovedFiles: 1, }, { @@ -1655,7 +1476,7 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) { }, }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, expectedNumRemovedFiles: 2, }, } @@ -1693,8 +1514,8 @@ func TestHelmState_DiffReleasesCleanup(t *testing.T) { } func TestHelmState_UpdateDeps(t *testing.T) { - helm := &mockHelmExec{ - updateDepsCallbacks: map[string]func(string) error{}, + helm := &exectest.Helm{ + UpdateDepsCallbacks: map[string]func(string) error{}, } var generatedDir string @@ -1704,7 +1525,7 @@ func TestHelmState_UpdateDeps(t *testing.T) { if err != nil { return "", err } - helm.updateDepsCallbacks[generatedDir] = func(chart string) error { + helm.UpdateDepsCallbacks[generatedDir] = func(chart string) error { content := []byte(`dependencies: - name: envoy repository: https://kubernetes-charts.storage.googleapis.com @@ -1763,8 +1584,8 @@ generated: 2019-05-16T15:42:45.50486+09:00 errs := state.UpdateDeps(helm) want := []string{"/", "/examples", "/helmfile", "/src/published", generatedDir} - if !reflect.DeepEqual(helm.charts, want) { - t.Errorf("HelmState.UpdateDeps() = %v, want %v", helm.charts, want) + if !reflect.DeepEqual(helm.Charts, want) { + t.Errorf("HelmState.UpdateDeps() = %v, want %v", helm.Charts, want) } if len(errs) != 0 { t.Errorf("HelmState.UpdateDeps() - no errors, but got %d: %v", len(errs), errs) @@ -1833,8 +1654,8 @@ func TestHelmState_ReleaseStatuses(t *testing.T) { tests := []struct { name string releases []ReleaseSpec - helm *mockHelmExec - want []mockRelease + helm *exectest.Helm + want []exectest.Release wantErr bool }{ { @@ -1844,8 +1665,10 @@ func TestHelmState_ReleaseStatuses(t *testing.T) { Name: "releaseA", }, }, - helm: &mockHelmExec{}, - want: []mockRelease{{"releaseA", []string{}}}, + helm: &exectest.Helm{}, + want: []exectest.Release{ + {Name: "releaseA", Flags: []string{}}, + }, }, { name: "happy path", @@ -1854,7 +1677,7 @@ func TestHelmState_ReleaseStatuses(t *testing.T) { Name: "error", }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, wantErr: true, }, { @@ -1867,7 +1690,7 @@ func TestHelmState_ReleaseStatuses(t *testing.T) { }, }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, wantErr: true, }, { @@ -1881,7 +1704,7 @@ func TestHelmState_ReleaseStatuses(t *testing.T) { Installed: boolValue(false), }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, wantErr: false, }, { @@ -1892,8 +1715,10 @@ func TestHelmState_ReleaseStatuses(t *testing.T) { TillerNamespace: "tillerns", }, }, - helm: &mockHelmExec{}, - want: []mockRelease{{"releaseA", []string{"--tiller-namespace", "tillerns"}}}, + helm: &exectest.Helm{}, + want: []exectest.Release{ + {Name: "releaseA", Flags: []string{"--tiller-namespace", "tillerns"}}, + }, }, } for i := range tests { @@ -1920,8 +1745,8 @@ func TestHelmState_ReleaseStatuses(t *testing.T) { t.Errorf("ReleaseStatuses() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr) return } - if !reflect.DeepEqual(tt.helm.releases, tt.want) { - t.Errorf("HelmState.ReleaseStatuses() for [%s] = %v, want %v", tt.name, tt.helm.releases, tt.want) + if !reflect.DeepEqual(tt.helm.Releases, tt.want) { + t.Errorf("HelmState.ReleaseStatuses() for [%s] = %v, want %v", tt.name, tt.helm.Releases, tt.want) } } t.Run(tt.name, f) @@ -1933,8 +1758,8 @@ func TestHelmState_TestReleasesNoCleanUp(t *testing.T) { name string cleanup bool releases []ReleaseSpec - helm *mockHelmExec - want []mockRelease + helm *exectest.Helm + want []exectest.Release wantErr bool tillerNamespace string }{ @@ -1945,8 +1770,8 @@ func TestHelmState_TestReleasesNoCleanUp(t *testing.T) { Name: "releaseA", }, }, - helm: &mockHelmExec{}, - want: []mockRelease{{"releaseA", []string{"--timeout", "1"}}}, + helm: &exectest.Helm{}, + want: []exectest.Release{{Name: "releaseA", Flags: []string{"--timeout", "1"}}}, }, { name: "do cleanup", @@ -1956,8 +1781,8 @@ func TestHelmState_TestReleasesNoCleanUp(t *testing.T) { Name: "releaseB", }, }, - helm: &mockHelmExec{}, - want: []mockRelease{{"releaseB", []string{"--cleanup", "--timeout", "1"}}}, + helm: &exectest.Helm{}, + want: []exectest.Release{{Name: "releaseB", Flags: []string{"--cleanup", "--timeout", "1"}}}, }, { name: "happy path", @@ -1966,7 +1791,7 @@ func TestHelmState_TestReleasesNoCleanUp(t *testing.T) { Name: "error", }, }, - helm: &mockHelmExec{}, + helm: &exectest.Helm{}, wantErr: true, }, { @@ -1977,8 +1802,8 @@ func TestHelmState_TestReleasesNoCleanUp(t *testing.T) { TillerNamespace: "tillerns", }, }, - helm: &mockHelmExec{}, - want: []mockRelease{{"releaseA", []string{"--timeout", "1", "--tiller-namespace", "tillerns"}}}, + helm: &exectest.Helm{}, + want: []exectest.Release{{Name: "releaseA", Flags: []string{"--timeout", "1", "--tiller-namespace", "tillerns"}}}, }, } for i := range tests { @@ -1993,8 +1818,8 @@ func TestHelmState_TestReleasesNoCleanUp(t *testing.T) { t.Errorf("TestReleases() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr) return } - if !reflect.DeepEqual(tt.helm.releases, tt.want) { - t.Errorf("HelmState.TestReleases() for [%s] = %v, want %v", tt.name, tt.helm.releases, tt.want) + if !reflect.DeepEqual(tt.helm.Releases, tt.want) { + t.Errorf("HelmState.TestReleases() for [%s] = %v, want %v", tt.name, tt.helm.Releases, tt.want) } } t.Run(tt.name, f) @@ -2053,7 +1878,7 @@ func TestHelmState_NoReleaseMatched(t *testing.T) { func TestHelmState_Delete(t *testing.T) { tests := []struct { name string - deleted []mockRelease + deleted []exectest.Release wantErr bool desired *bool installed bool @@ -2069,7 +1894,7 @@ func TestHelmState_Delete(t *testing.T) { desired: boolValue(true), installed: true, purge: false, - deleted: []mockRelease{{"releaseA", []string{}}}, + deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}}, }, { name: "desired(default) and installed (purge=false)", @@ -2077,7 +1902,7 @@ func TestHelmState_Delete(t *testing.T) { desired: nil, installed: true, purge: false, - deleted: []mockRelease{{"releaseA", []string{}}}, + deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}}, }, { name: "desired(default) and installed (purge=false) but error", @@ -2085,7 +1910,7 @@ func TestHelmState_Delete(t *testing.T) { desired: nil, installed: true, purge: false, - deleted: []mockRelease{{"releaseA", []string{}}}, + deleted: []exectest.Release{{Name: "releaseA", Flags: []string{}}}, }, { name: "desired and installed (purge=true)", @@ -2093,7 +1918,7 @@ func TestHelmState_Delete(t *testing.T) { desired: boolValue(true), installed: true, purge: true, - deleted: []mockRelease{{"releaseA", []string{"--purge"}}}, + deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--purge"}}}, }, { name: "desired but not installed (purge=false)", @@ -2101,7 +1926,7 @@ func TestHelmState_Delete(t *testing.T) { desired: boolValue(true), installed: false, purge: false, - deleted: []mockRelease{}, + deleted: []exectest.Release{}, }, { name: "desired but not installed (purge=true)", @@ -2109,7 +1934,7 @@ func TestHelmState_Delete(t *testing.T) { desired: boolValue(true), installed: false, purge: true, - deleted: []mockRelease{}, + deleted: []exectest.Release{}, }, { name: "installed but filtered (purge=false)", @@ -2117,7 +1942,7 @@ func TestHelmState_Delete(t *testing.T) { desired: boolValue(false), installed: true, purge: false, - deleted: []mockRelease{}, + deleted: []exectest.Release{}, }, { name: "installed but filtered (purge=true)", @@ -2125,7 +1950,7 @@ func TestHelmState_Delete(t *testing.T) { desired: boolValue(false), installed: true, purge: true, - deleted: []mockRelease{}, + deleted: []exectest.Release{}, }, { name: "not installed, and filtered (purge=false)", @@ -2133,7 +1958,7 @@ func TestHelmState_Delete(t *testing.T) { desired: boolValue(false), installed: false, purge: false, - deleted: []mockRelease{}, + deleted: []exectest.Release{}, }, { name: "not installed, and filtered (purge=true)", @@ -2141,7 +1966,7 @@ func TestHelmState_Delete(t *testing.T) { desired: boolValue(false), installed: false, purge: true, - deleted: []mockRelease{}, + deleted: []exectest.Release{}, }, { name: "with tiller args", @@ -2151,7 +1976,7 @@ func TestHelmState_Delete(t *testing.T) { purge: true, tillerNamespace: "tillerns", flags: "--tiller-namespacetillerns", - deleted: []mockRelease{{"releaseA", []string{"--purge", "--tiller-namespace", "tillerns"}}}, + deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--purge", "--tiller-namespace", "tillerns"}}}, }, { name: "with kubecontext", @@ -2161,7 +1986,7 @@ func TestHelmState_Delete(t *testing.T) { purge: true, kubeContext: "ctx", flags: "--kube-contextctx", - deleted: []mockRelease{{"releaseA", []string{"--purge", "--kube-context", "ctx"}}}, + deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--purge", "--kube-context", "ctx"}}}, }, { name: "with default kubecontext", @@ -2171,7 +1996,7 @@ func TestHelmState_Delete(t *testing.T) { purge: true, defKubeContext: "defctx", flags: "--kube-contextdefctx", - deleted: []mockRelease{{"releaseA", []string{"--purge", "--kube-context", "defctx"}}}, + deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--purge", "--kube-context", "defctx"}}}, }, { name: "with non-default and default kubecontexts", @@ -2182,7 +2007,7 @@ func TestHelmState_Delete(t *testing.T) { kubeContext: "ctx", defKubeContext: "defctx", flags: "--kube-contextctx", - deleted: []mockRelease{{"releaseA", []string{"--purge", "--kube-context", "ctx"}}}, + deleted: []exectest.Release{{Name: "releaseA", Flags: []string{"--purge", "--kube-context", "ctx"}}}, }, } for i := range tests { @@ -2208,12 +2033,12 @@ func TestHelmState_Delete(t *testing.T) { Releases: releases, logger: logger, } - helm := &mockHelmExec{ - lists: map[listKey]string{}, - deleted: []mockRelease{}, + helm := &exectest.Helm{ + Lists: map[exectest.ListKey]string{}, + Deleted: []exectest.Release{}, } if tt.installed { - helm.lists[listKey{filter: "^" + name + "$", flags: tt.flags}] = name + helm.Lists[exectest.ListKey{Filter: "^" + name + "$", Flags: tt.flags}] = name } affectedReleases := AffectedReleases{} errs := state.DeleteReleases(&affectedReleases, helm, 1, tt.purge) @@ -2222,8 +2047,8 @@ func TestHelmState_Delete(t *testing.T) { t.Errorf("DeleteReleases() for %s error = %v, wantErr %v", tt.name, errs, tt.wantErr) return } - } else if !(reflect.DeepEqual(tt.deleted, helm.deleted) && (len(affectedReleases.Deleted) == len(tt.deleted))) { - t.Errorf("unexpected deletions happened: expected %v, got %v", tt.deleted, helm.deleted) + } else if !(reflect.DeepEqual(tt.deleted, helm.Deleted) && (len(affectedReleases.Deleted) == len(tt.deleted))) { + t.Errorf("unexpected deletions happened: expected %v, got %v", tt.deleted, helm.Deleted) } } t.Run(tt.name, f) diff --git a/pkg/testhelper/diff.go b/pkg/testhelper/diff.go new file mode 100644 index 00000000..3285daa0 --- /dev/null +++ b/pkg/testhelper/diff.go @@ -0,0 +1,103 @@ +package testhelper + +import ( + "bytes" + "fmt" + "io" + "math" + "strings" + + "github.com/aryann/difflib" +) + +func Diff(want, got string, context int) (string, bool) { + records := difflib.Diff( + strings.Split(want, "\n"), + strings.Split(got, "\n"), + ) + + w := &bytes.Buffer{} + + changed := checkAndPrintRecords(w, records, context) + + return w.String(), changed +} + +func checkAndPrintRecords(w io.Writer, records []difflib.DiffRecord, context int) bool { + var changed bool + if context >= 0 { + distances := calculateDistances(records) + omitting := false + for i, diff := range records { + if diff.Delta != difflib.Common { + changed = true + } + if distances[i] > context { + if !omitting { + fmt.Fprintln(w, "...") + omitting = true + } + } else { + omitting = false + fmt.Fprintln(w, formatRecord(diff)) + } + } + } else { + for _, diff := range records { + if diff.Delta != difflib.Common { + changed = true + } + fmt.Fprintln(w, formatRecord(diff)) + } + } + return changed +} + +func formatRecord(diff difflib.DiffRecord) string { + var prefix string + switch diff.Delta { + case difflib.RightOnly: + prefix = "+ " + case difflib.LeftOnly: + prefix = "- " + case difflib.Common: + prefix = " " + } + + return prefix + diff.Payload +} + +// Shamelessly and thankfully copied from https://github.com/databus23/helm-diff/blob/99b8474af7726ca6f57b37b0b8b8f3cd36c991e8/diff/diff.go#L116 +func calculateDistances(diffs []difflib.DiffRecord) map[int]int { + distances := map[int]int{} + + // Iterate forwards through diffs, set 'distance' based on closest 'change' before this line + change := -1 + for i, diff := range diffs { + if diff.Delta != difflib.Common { + change = i + } + distance := math.MaxInt32 + if change != -1 { + distance = i - change + } + distances[i] = distance + } + + // Iterate backwards through diffs, reduce 'distance' based on closest 'change' after this line + change = -1 + for i := len(diffs) - 1; i >= 0; i-- { + diff := diffs[i] + if diff.Delta != difflib.Common { + change = i + } + if change != -1 { + distance := change - i + if distance < distances[i] { + distances[i] = distance + } + } + } + + return distances +}