├── .circleci └── config.yml ├── .gitignore ├── .goreleaser.yml ├── LICENSE.txt ├── Makefile ├── README.md ├── examples ├── README.md ├── helmfile │ ├── Makefile │ ├── README.md │ ├── helmfile.yaml │ └── values.yaml ├── hpa.jsonpatch.yaml ├── hpa.strategicmergepatch.yaml ├── kustomize │ ├── kustomization.yaml │ ├── resources │ │ ├── myapp.pod.yaml │ │ └── mysql.pod.yaml │ ├── templates │ │ └── tests │ │ │ └── test-mysql-connection.yaml │ ├── values.2.yaml │ └── values.yaml ├── manifests │ └── pod.yaml └── myinject ├── go.mod ├── go.sum ├── hack └── semtag ├── helm_fallback.go ├── install-binary.sh ├── main.go ├── pkg ├── cmdsite │ └── cmdsite.go ├── helmx │ ├── adopt.go │ ├── chartify.go │ ├── diff.go │ ├── doc.go │ ├── helm3.go │ ├── helmx.go │ ├── option.go │ ├── render.go │ ├── run_cmd.go │ ├── runner.go │ ├── upgrade.go │ └── yaml.go ├── releasetool │ ├── doc.go │ ├── helm_decode_release.go │ ├── helm_makekey.go │ ├── helm_release.go │ ├── helm_release_cfgmap.go │ ├── helm_release_labels.go │ ├── helm_release_secret.yaml.go │ ├── hooks.go │ └── releasetool.go ├── testcmdsite │ ├── testcmdsite.go │ └── testcmdsite_test.go └── util │ └── util.go └── plugin.yaml /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | test: 4 | docker: 5 | - image: circleci/golang:1.12.4 6 | environment: 7 | GOPATH: /go 8 | steps: 9 | - checkout 10 | - run: go env 11 | - run: pwd 12 | - restore_cache: 13 | keys: 14 | - go-mod-cache-v1-{{ checksum "./go.sum" }} 15 | - go-mod-cache-v1- 16 | - run: go mod download 17 | - run: make helm test 18 | - save_cache: 19 | key: go-mod-cache-v1-{{ checksum "./go.sum" }} 20 | paths: 21 | - /go/pkg/mod 22 | - persist_to_workspace: 23 | root: / 24 | paths: 25 | - go 26 | release: 27 | docker: 28 | - image: circleci/golang:1.12.4 29 | steps: 30 | - checkout 31 | # We can't use attach_workpace due to that CircleCI skips `test` when it is already run before tagging 32 | - restore_cache: 33 | keys: 34 | - go-mod-cache-v1-{{ checksum "./go.sum" }} 35 | - go-mod-cache-v1- 36 | - run: curl -sL https://git.io/goreleaser | bash 37 | workflows: 38 | version: 2 39 | build: 40 | jobs: 41 | - test 42 | release: 43 | jobs: 44 | - test: 45 | filters: 46 | branches: 47 | ignore: /.*/ 48 | tags: 49 | only: /v[0-9]+(\.[0-9]+)*(-.*)*/ 50 | - release: 51 | requires: 52 | - test 53 | filters: 54 | branches: 55 | ignore: /.*/ 56 | tags: 57 | only: /v[0-9]+(\.[0-9]+)*(-.*)*/ 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _scratch 2 | inj 3 | inj.exe 4 | vendor/ 5 | dist/ 6 | *~ 7 | *.iml 8 | helm-x 9 | .idea 10 | .envrc 11 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: helm-x 2 | builds: 3 | - env: 4 | - CGO_ENABLED=0 5 | ldflags: 6 | - -s -w -X main.Version={{.Version}} 7 | changelog: 8 | filters: 9 | # commit messages matching the regexp listed here will be removed from 10 | # the changelog 11 | # Default is empty 12 | exclude: 13 | - '^docs:' 14 | - typo 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Helm Logs Plugin 2 | Copyright (C) 2016, Maor Friedman 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | HELM_HOME ?= $(shell helm home) 2 | BINARY_NAME ?= helm-x 3 | PLUGIN_NAME ?= helm-x 4 | HELM_PLUGIN_DIR ?= $(HELM_HOME)/plugins/helm-x 5 | VERSION := $(shell sed -n -e 's/version:[ "]*\([^"]*\).*/\1/p' plugin.yaml) 6 | DIST := $(CURDIR)/_dist 7 | LDFLAGS := "-X main.Version=${VERSION}" 8 | 9 | .PHONY: helm 10 | helm: 11 | curl -LO https://git.io/get_helm.sh 12 | chmod 700 get_helm.sh 13 | ./get_helm.sh 14 | 15 | .PHONY: install 16 | install: build 17 | mkdir -p $(HELM_PLUGIN_DIR)/bin 18 | cp $(BINARY_NAME) $(HELM_PLUGIN_DIR)/bin/ 19 | cp plugin.yaml $(HELM_PLUGIN_DIR)/ 20 | 21 | .PHONY: uninstall 22 | uninstall: 23 | echo "would you mind removing $(HELM_PLUGIN_DIR)/ by yourself? :)" 24 | 25 | .PHONY: hookInstall 26 | hookInstall: build 27 | 28 | .PHONY: format 29 | format: 30 | test -z "$$(find . -path ./vendor -prune -type f -o -name '*.go' -exec gofmt -d {} + | tee /dev/stderr)" || \ 31 | test -z "$$(find . -path ./vendor -prune -type f -o -name '*.go' -exec gofmt -w {} + | tee /dev/stderr)" 32 | 33 | .PHONY: test 34 | test: build 35 | go test ./... 36 | 37 | .PHONY: build 38 | build: 39 | go build -o $(BINARY_NAME) -ldflags $(LDFLAGS) ./ 40 | 41 | .PHONY: dist 42 | dist: 43 | mkdir -p $(DIST) 44 | CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o $(BINARY_NAME) -ldflags $(LDFLAGS) ./main.go 45 | tar -zcvf $(DIST)/$(PLUGIN_NAME)-arm64-$(VERSION).tgz $(BINARY_NAME) README.md LICENSE.txt plugin.yaml 46 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o $(BINARY_NAME) -ldflags $(LDFLAGS) ./main.go 47 | tar -zcvf $(DIST)/$(PLUGIN_NAME)-linux-$(VERSION).tgz $(BINARY_NAME) README.md LICENSE.txt plugin.yaml 48 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o $(BINARY_NAME) -ldflags $(LDFLAGS) ./main.go 49 | tar -zcvf $(DIST)/$(PLUGIN_NAME)-macos-$(VERSION).tgz $(BINARY_NAME) README.md LICENSE.txt plugin.yaml 50 | CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o $(BINARY_NAME).exe -ldflags $(LDFLAGS) ./main.go 51 | tar -zcvf $(DIST)/$(PLUGIN_NAME)-windows-$(VERSION).tgz $(BINARY_NAME).exe README.md LICENSE.txt plugin.yaml 52 | rm inj 53 | rm inj.exe 54 | 55 | release/minor: 56 | git fetch origin master 57 | bash -c 'if git branch | grep autorelease; then git branch -D autorelease; else echo no branch to be cleaned; fi' 58 | git checkout -b autorelease origin/master 59 | git branch -D master || echo "no master branch found. skipping deletion" 60 | git branch -m autorelease master 61 | hack/semtag final -s minor 62 | 63 | release/patch: 64 | git fetch origin master 65 | bash -c 'if git branch | grep autorelease; then git branch -D autorelease; else echo no branch to be cleaned; fi' 66 | git checkout -b autorelease origin/master 67 | git branch -D master || echo "no master branch found. skipping deletion" 68 | git branch -m autorelease master 69 | hack/semtag final -s patch 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Helm X Plugin 2 | 3 | > NOTE: The most important part of this plugin, "chartification" that turns a set of Kubernetes manifests or a kustomization into a temporary chart, is extracted to https://github.com/variantdev/chartify and maintained there. PTAL! 4 | 5 | No more "Kustomize vs Helm". 6 | 7 | `helm-x` makes `helm` better integrate with vanilla Kubernetes manifests, [kustomize](https://kustomize.io/), and manual sidecar injections. 8 | 9 | --- 10 | 11 | With `helm-x`, you can install and sidecar-inject helm charts, manifests, kustomize apps in the same way. 12 | 13 | Installing your kustomize app as a helm chart is as easy as running: 14 | 15 | ``` 16 | $ helm x apply myapp examples/kustomize --version 1.2.3 \ 17 | -f examples/kustomize/values.yaml 18 | ``` 19 | 20 | Then you can even run a [helm test](https://github.com/helm/helm/blob/master/docs/chart_tests.md): 21 | 22 | ``` 23 | $ helm test myapp 24 | RUNNING: myapp-test 25 | PASSED: myapp-test 26 | ``` 27 | 28 | Show diff before further upgrade: 29 | 30 | ``` 31 | $ helm x diff myapp examples/kustomize --version 1.2.4 \ 32 | -f examples/kustomize/values.2.yaml 33 | ``` 34 | 35 | Add an adhoc dependency: 36 | 37 | ``` 38 | $ helm x diff myapp examples/kustomize --version 1.2.4 \ 39 | -f examples/kustomize/values.2.yaml \ 40 | --adhoc-dependency $ALIAS=stable/mysql:$CHART_VER 41 | ``` 42 | 43 | Apply JSON patches or Strategic-Merge patches to K8s resources being `helm install`ed: 44 | 45 | ``` 46 | # See examples/hpa.strategicmergepatch.yaml and examples/hpa.jsonpatch.yaml for examples 47 | $ helm x diff myapp examples/kustomize --version 1.2.4 \ 48 | -f examples/kustomize/values.2.yaml \ 49 | --adhoc-dependency $ALIAS=stable/mysql:$CHART_VER \ 50 | --strategic-merge-patch path/to/strategicmerge.patch.yaml \ 51 | --jsonpatch path/to/json.patch.yaml 52 | ``` 53 | 54 | Check out the examples in the [examples](/examples) directory! 55 | 56 | --- 57 | 58 | It keeps all the good things of `helm` as an extendable package manager. 59 | 60 | That is, `helm x apply` is able to automatically remove resources that have gone from the desired state, without any additions like `--prune` and `--prune-whitelist` of `kubectl apply`. 61 | 62 | Also, you can leverage useful `helm` commands like below, even though your app is written with a tool like `kustomize`: 63 | 64 | - [`helm diff`](https://github.com/databus23/helm-diff) to diff your app 65 | - [`helm test`](https://github.com/helm/helm/blob/master/docs/chart_tests.md) to test your app 66 | - [`helm list`](https://helm.sh/docs/helm/#helm-list) to list all the apps running on your cluster, written with whatever tool not only `helm`. 67 | - [`helm package`](https://helm.sh/docs/helm/#helm-package) to package your app into a helm chart(even if it was originally written as a `kustomization`) 68 | - [`helm s3`](https://github.com/hypnoglow/helm-s3) to stored packaged apps into AWS S3 as a chart registry(even if your app is written with `kustomize`) 69 | - [`helm tiller`](https://github.com/rimusz/helm-tiller) to go tillerless! 70 | 71 | If you're familiar with `helm`, what makes `helm-x` unique is it runs `helm upgrade --install` to install your apps described as: 72 | 73 | 1. kustomizations 74 | 2. directories containing manifests 75 | 3. local and remote helm charts. 76 | 77 | ## Usage 78 | 79 | ### helm x upgrade(a.k.a helm x apply) 80 | 81 | Install or upgrade the helm release from the directory or the chart specified. 82 | 83 | Under the hood, this generates Kubernetes manifests from (1)directory containing manifests/kustomization/local helm chart or (2)remote helm chart, then inject sidecars, and finally install the result as a Helm release 84 | 85 | When DIR_OR_CHART is a local helm chart, this copies it into a temporary directory, renders all the templates into manifests by running "helm template", and then run injectors to update manifests, and install the temporary chart by running "helm upgrade --install". 86 | 87 | It's better than installing it with "kubectl apply -f", as you can leverage various helm sub-commands like "helm test" if you included tests in the "templates/tests" directory of the chart. 88 | It's also better in regard to security and reproducibility, as creating a helm release allows helm to detect Kubernetes resources removed from the desired state but still exist in the cluster, and automatically delete unnecessary resources. 89 | 90 | When DIR_OR_CHART is a local directory containing Kubernetes manifests, this copies all the manifests into a temporary directory, and turns it into a local Helm chart by generating a Chart.yaml whose version and appVersion are set to the value of the --version flag. 91 | 92 | When DIR_OR_CHART contains kustomization.yaml, this runs "kustomize build" to generate manifests, and then run injectors to update manifests, and install the temporary chart by running "helm upgrade --install". 93 | 94 | ```console 95 | Usage: 96 | helm-x apply [RELEASE] [DIR_OR_CHART] [flags] 97 | 98 | Flags: 99 | --adhoc-dependency stringArray Adhoc dependencies to be added to the temporary local helm chart being installed. Syntax: ALIAS=REPO/CHART:VERSION e.g. mydb=stable/mysql:1.2.3 100 | --adopt strings adopt existing k8s resources before apply 101 | --debug enable verbose output 102 | --dry-run simulate an upgrade 103 | -h, --help help for apply 104 | --inject 'istioctl kube-inject -f FILE' injector to use (must be pre-installed) and flags to be passed in the syntax of 'istioctl kube-inject -f FILE'. "FILE" is replaced with the Kubernetes manifest file being injected 105 | --injector --inject "CMD ARG1 ARG2" DEPRECATED: Use --inject "CMD ARG1 ARG2" instead. injector to use (must be pre-installed) and flags to be passed in the syntax of `'CMD SUBCMD,FLAG1=VAL1,FLAG2=VAL2'`. Flags should be without leading "--" (can specify multiple). "FILE" in values are replaced with the Kubernetes manifest file being injected. Example: "--injector 'istioctl kube-inject f=FILE,injectConfigFile=inject-config.yaml,meshConfigFile=mesh.config.yaml" 106 | --install install the release if missing (default true) 107 | --json-patch stringArray Kustomize JSON Patch file to be applied to the rendered K8s manifests. Allows customizing your chart without forking or updating 108 | --kubecontext string name of the kubeconfig context to use 109 | --namespace string namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace 110 | --set stringArray set values on the command line (can specify multiple) 111 | --strategic-merge-patch stringArray Kustomize Strategic Merge Patch file to be applied to the rendered K8s manifests. Allows customizing your chart without forking or updating 112 | --tiller-namespace string namespace to in which release configmap/secret objects reside (default "kube-system") 113 | --timeout int time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks) (default 300) 114 | --tls enable TLS for request 115 | --tls-cert string path to TLS certificate file (default: $HELM_HOME/cert.pem) 116 | --tls-key string path to TLS key file (default: $HELM_HOME/key.pem) 117 | -f, --values stringArray specify values in a YAML file or a URL (can specify multiple) 118 | --version string specify the exact chart version to use. If this is not specified, the latest version is used 119 | ``` 120 | 121 | ### helm x diff 122 | 123 | Show a diff explaining what `helm x apply` would change. 124 | 125 | Under the hood, this generates Kubernetes manifests from (1)directory containing manifests/kustomization/local helm chart or (2)remote helm chart, then inject sidecars, and finally print the resulting manifests 126 | 127 | When DIR_OR_CHART is a local helm chart, this copies it into a temporary directory, renders all the templates into manifests by running "helm template", and then run injectors to update manifests, and prints the results. 128 | 129 | When DIR_OR_CHART is a local directory containing Kubernetes manifests, this copies all the manifests into a temporary directory, and turns it into a local Helm chart by generating a Chart.yaml whose version and appVersion are set to the value of the --version flag. 130 | 131 | When DIR_OR_CHART contains kustomization.yaml, this runs "kustomize build" to generate manifests, and then run injectors to update manifests, and prints the results. 132 | 133 | ```console 134 | Usage: 135 | helm-x diff [RELEASE] [DIR_OR_CHART] [flags] 136 | 137 | Flags: 138 | --adhoc-dependency stringArray Adhoc dependencies to be added to the temporary local helm chart being installed. Syntax: ALIAS=REPO/CHART:VERSION e.g. mydb=stable/mysql:1.2.3 139 | --debug enable verbose output 140 | -h, --help help for diff 141 | --inject 'istioctl kube-inject -f FILE' injector to use (must be pre-installed) and flags to be passed in the syntax of 'istioctl kube-inject -f FILE'. "FILE" is replaced with the Kubernetes manifest file being injected 142 | --injector --inject "CMD ARG1 ARG2" DEPRECATED: Use --inject "CMD ARG1 ARG2" instead. injector to use (must be pre-installed) and flags to be passed in the syntax of `'CMD SUBCMD,FLAG1=VAL1,FLAG2=VAL2'`. Flags should be without leading "--" (can specify multiple). "FILE" in values are replaced with the Kubernetes manifest file being injected. Example: "--injector 'istioctl kube-inject f=FILE,injectConfigFile=inject-config.yaml,meshConfigFile=mesh.config.yaml" 143 | --json-patch stringArray Kustomize JSON Patch file to be applied to the rendered K8s manifests. Allows customizing your chart without forking or updating 144 | --kubecontext string name of the kubeconfig context to use 145 | --namespace string namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace 146 | --set stringArray set values on the command line (can specify multiple) 147 | --strategic-merge-patch stringArray Kustomize Strategic Merge Patch file to be applied to the rendered K8s manifests. Allows customizing your chart without forking or updating 148 | --tiller-namespace string namespace to in which release configmap/secret objects reside (default "kube-system") 149 | --tls enable TLS for request 150 | --tls-cert string path to TLS certificate file (default: $HELM_HOME/cert.pem) 151 | --tls-key string path to TLS key file (default: $HELM_HOME/key.pem) 152 | -f, --values stringArray specify values in a YAML file or a URL (can specify multiple) 153 | --version string specify the exact chart version to use. If this is not specified, the latest version is used 154 | ``` 155 | 156 | ### helm x template 157 | 158 | Print Kubernetes manifests that would be generated by `helm x apply` 159 | 160 | Under the hood, this generates Kubernetes manifests from (1)directory containing manifests/kustomization/local helm chart or (2)remote helm chart, then inject sidecars, and finally print the resulting manifests 161 | 162 | When DIR_OR_CHART is a local helm chart, this copies it into a temporary directory, renders all the templates into manifests by running "helm template", and then run injectors to update manifests, and prints the results. 163 | 164 | When DIR_OR_CHART is a local directory containing Kubernetes manifests, this copies all the manifests into a temporary directory, and turns it into a local Helm chart by generating a Chart.yaml whose version and appVersion are set to the value of the --version flag. 165 | 166 | When DIR_OR_CHART contains kustomization.yaml, this runs "kustomize build" to generate manifests, and then run injectors to update manifests, and prints the results. 167 | 168 | ```console 169 | Usage: 170 | helm-x template [DIR_OR_CHART] [flags] 171 | 172 | Flags: 173 | --adhoc-dependency stringArray Adhoc dependencies to be added to the temporary local helm chart being installed. Syntax: ALIAS=REPO/CHART:VERSION e.g. mydb=stable/mysql:1.2.3 174 | --as-release helm [upgrade|install] turn the result into a proper helm release, by removing hooks from the manifest, and including a helm release configmap/secret that should otherwise created by helm [upgrade|install] 175 | --debug enable verbose output 176 | -h, --help help for template 177 | --inject 'istioctl kube-inject -f FILE' injector to use (must be pre-installed) and flags to be passed in the syntax of 'istioctl kube-inject -f FILE'. "FILE" is replaced with the Kubernetes manifest file being injected 178 | --injector --inject "CMD ARG1 ARG2" DEPRECATED: Use --inject "CMD ARG1 ARG2" instead. injector to use (must be pre-installed) and flags to be passed in the syntax of `'CMD SUBCMD,FLAG1=VAL1,FLAG2=VAL2'`. Flags should be without leading "--" (can specify multiple). "FILE" in values are replaced with the Kubernetes manifest file being injected. Example: "--injector 'istioctl kube-inject f=FILE,injectConfigFile=inject-config.yaml,meshConfigFile=mesh.config.yaml" 179 | --json-patch stringArray Kustomize JSON Patch file to be applied to the rendered K8s manifests. Allows customizing your chart without forking or updating 180 | --kubecontext string name of the kubeconfig context to use 181 | --name string release name (default "release-name") (default "release-name") 182 | --namespace string namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace 183 | --set stringArray set values on the command line (can specify multiple) 184 | --strategic-merge-patch stringArray Kustomize Strategic Merge Patch file to be applied to the rendered K8s manifests. Allows customizing your chart without forking or updating 185 | --tiller-namespace string namespace to in which release configmap/secret objects reside (default "kube-system") 186 | --tiller-namsepace string namespace in which release confgimap/secret objects reside (default "kube-system") 187 | -f, --values stringArray specify values in a YAML file or a URL (can specify multiple) 188 | --version string specify the exact chart version to use. If this is not specified, the latest version is used 189 | ``` 190 | 191 | ### helm x adopt 192 | 193 | Adopt a set of existing K8s resources as if they are installed originally as a Helm chart. 194 | 195 | ```console 196 | Adopt the existing kubernetes resources as a helm release 197 | 198 | RESOURCES are represented as a whitespace-separated list of kind/name, like: 199 | 200 | configmap/foo.v1 secret/bar deployment/myapp 201 | 202 | So that the full command looks like: 203 | 204 | helm x adopt myrelease configmap/foo.v1 secret/bar deployment/myapp 205 | 206 | Usage: 207 | helm-x adopt [RELEASE] [RESOURCES]... [flags] 208 | 209 | Flags: 210 | -h, --help help for adopt 211 | --kubecontext string the kubeconfig context to use 212 | --namespace string The namespace in which the resources to be adopted reside 213 | --tiller-namespace string the tiller namespaceto use (default "kube-system") 214 | --tls enable TLS for request 215 | --tls-cert string path to TLS certificate file (default: $HELM_HOME/cert.pem) 216 | --tls-key string path to TLS key file (default: $HELM_HOME/key.pem) 217 | ``` 218 | 219 | ## Install 220 | 221 | ``` 222 | $ helm plugin install https://github.com/mumoshu/helm-x 223 | ``` 224 | 225 | The above will fetch the latest binary release of `helm-x` and install it. 226 | 227 | ### Developer (From Source) Install 228 | 229 | If you would like to handle the build yourself, instead of fetching a binary, this is how recommend doing it. 230 | 231 | First, set up your environment: 232 | 233 | - You need to have [Go](http://golang.org) 1.12 or greater installed. 234 | 235 | Clone this repo anywhere OUSTSIDE of your `$GOPATH`: 236 | 237 | ``` 238 | $ git clone git@github.com:mumoshu/helm-x 239 | $ make install 240 | ``` 241 | 242 | If you don't want to install it as a helm plugin, you can still run it stand-alone: 243 | 244 | ``` 245 | $ make build 246 | $ ./helm-x template --name myapp examples/manifests/ --version 1.2.3 247 | $ ./helm-x diff myapp examples/manifests/ --version 1.2.3 --debug 248 | $ ./helm-x apply myapp examples/manifests/ --version 1.2.3 --debug 249 | ``` 250 | 251 | ## Notes 252 | 253 | * Not all flags present in the original `helm diff`, `helm template`, `helm upgrade` flags are implemented. If you need any other flags, please feel free to open issues and even submit pull requests. 254 | * If you are using the `--kube-context` flag, you need to change it to `--kubecontext`, since helm plugins [drop this flag](https://github.com/helm/helm/blob/master/docs/plugins.md#a-note-on-flag-parsing). 255 | 256 | ## Prior Arts 257 | 258 | 1. [Customizing Upstream Helm Charts with Kustomize | Testing Clouds at 128bpm](https://testingclouds.wordpress.com/2018/07/20/844/) 259 | 260 | Relies on `helm template` to generate K8s manifests that `kustomize` can work on. 261 | This method implies that you can't use `helm` for release management, as also explained in the original article as follows: 262 | > First, Helm is no longer controlling releases of manifests into the cluster. This means that you cannot use helm rollback or helm list or any of the helm release related commands to manage your deployments. 263 | 264 | `helm-x`, on the other hand, solves this issue by treating the final output of `kustomize` as a temporary Helm chart, and actually `helm-install` it. 265 | 266 | ## Acknowledgements 267 | 268 | This project's implementation has been largely inspired from the awesome [helm-inject](https://github.com/maorfr/helm-inject) project maintained by @maorfr. 269 | Thanks a lot for your work, @maorfr! 270 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | 3 | ### kustomize 4 | 5 | An example project defined with `kustomize`. 6 | 7 | ``` 8 | $ helm x apply mypap example/kustomize --version 1.2.3 9 | ``` 10 | 11 | If you want to override some params in `kustomization.yaml`: 12 | 13 | ``` 14 | $ kubectl create ns mykustomizeapp 15 | $ helm x apply myapp examples/kustomize --version 1.2.3 \ 16 | -f examples/kustomize/values.yaml 17 | ``` 18 | 19 | You can even add helm chart tests to your kustomize app: 20 | 21 | ``` 22 | $ helm test myapp 23 | RUNNING: myapp-test 24 | PASSED: myapp-test 25 | ``` 26 | 27 | And please do not forget to clean things up before proceeding to other examples: 28 | 29 | ``` 30 | $ helm delete --purge myapp 31 | $ kubectl delete ns mykustomizeapp 32 | ``` 33 | 34 | > Note that the current kustomize example creates forever-crash-looping pods, because I had not put much effort. 35 | > Please feel free to contribute a more serious, working example :) 36 | 37 | ### manifests 38 | 39 | An example project defined with a set of vanilla Kubernetes manifest files. 40 | 41 | ``` 42 | $ helm x apply myapp examples/manifests --version 1.2.3 43 | ``` 44 | 45 | If you're done, clean up by running: 46 | 47 | ``` 48 | $ helm delete --purge myapp 49 | ``` 50 | 51 | ### myinject 52 | 53 | An example injector that just adds a YAML comment. 54 | 55 | Use it like: 56 | 57 | ``` 58 | $ helm x apply myapp examples/manifests --version 1.2.3 \ 59 | --injector 'examples/myinject,FILE' 60 | ``` 61 | 62 | If you're done, clean up by running: 63 | 64 | ``` 65 | $ helm delete --purge myapp 66 | ``` 67 | -------------------------------------------------------------------------------- /examples/helmfile/Makefile: -------------------------------------------------------------------------------- 1 | dev/diff: 2 | (cd ../../ && make install) && helmfile --helm-binary ~/.helm/plugins/helm-x/bin/helm-x --log-level debug diff 3 | 4 | dev/apply: 5 | (cd ../../ && make install) && helmfile --helm-binary ~/.helm/plugins/helm-x/bin/helm-x --log-level debug apply 6 | -------------------------------------------------------------------------------- /examples/helmfile/README.md: -------------------------------------------------------------------------------- 1 | # Integrating helm-x with helmfile 2 | 3 | [helmfile](https://github.com/roboll/helmfile) is a kind of Infrastructure as Code tool for Kubernetes. It takes a declarative spec file called "state file"(`helmfile.yaml`) to reconcile your K8s cluster to the desired state declared in it. 4 | 5 | Have you ever dreamed of a Terraform-like tool for K8s, but more K8s specific features built-in? `helmfile` is! 6 | 7 | `helmfile` is only capable of managing the cluster state as the set of Helm releases, in other words a set of Helm charts coupled with the chart values. 8 | 9 | Integrating `helm-x` with `helmfile` allows you to manage not only Helm releases but also Kustomizations and vanilla K8s manifest directories. Theoretically it can be enhanced to support any other K8s deployment tool that is capable of generating K8s manifests! 10 | 11 | ## TL;DR; 12 | 13 | Run `helmfile` with `--helm-binary` pointed to the `helm-x` binary: 14 | 15 | ``` 16 | $ helmfile --helm-binary ~/.helm/plugins/helm-x/bin/helm-x --log-level debug apply 17 | ``` 18 | 19 | ## Rationale 20 | 21 | Comparing `kubectl`, `kustomize`, and `helm` is like comparing apples and oranges. `kubectl` is used to apply vanilla K8s manifests. `kustomize` is used to build K8s manifests. 22 | 23 | In contrast, `helm` is used to generate K8s manifests from helm charts/templates AND for test automation(`helm test`), reviewing change sets(`helm diff`), release management(`helm list`, `helm status`, `helm rollback`, `helm get values`, ...). 24 | 25 | From the developer's perspective, `kustomize` is ability to compose complex K8s manifests without a pile of YAML and go templates is appealing. But does it justify throwing away all the other things `helm` has already solved for us? Why we can't have both? 26 | 27 | `helm-x` is the answer. I blurs that border by enhancing `helm` to treat K8s manifests and Kustomizations as Helm charts. For example, after `helm x install`ing one, any release-related helm commands can be run on it. You can even add `templates/tests` directory containing helm tests even under your K8s manifests directory or your project, so that you can run `helm test` on the Helm release created from a manifests directory or Kustomize project! 28 | 29 | The integration of `helm-x` with `helmfile` advances it further. A well-known downside of `helm` is it's lack of declarativity. `helmfile` allows you to write state files called `helmfile.yaml` for your Helm releases. `helm-x` and `helmfile` together allows you to write state files for any combo of project types, including local and remote helm charts, k8s manifests directory, kustomize projects. 30 | -------------------------------------------------------------------------------- /examples/helmfile/helmfile.yaml: -------------------------------------------------------------------------------- 1 | releases: 2 | - name: kustomize 3 | chart: ../kustomize 4 | - name: manifests 5 | chart: ../manifests 6 | - name: foo 7 | chart: incubator/raw 8 | dependencies: 9 | - alias: bar 10 | chart: incubator/raw 11 | values: 12 | - values.yaml 13 | - bar: 14 | enabled: true 15 | resources: 16 | - apiVersion: v1 17 | kind: Pod 18 | metadata: 19 | name: bar 20 | spec: 21 | containers: 22 | - command: 23 | - sleep 24 | - 1000 25 | image: alpine:3.9.4 26 | imagePullPolicy: IfNotPresent 27 | name: bar 28 | jsonPatches: 29 | - target: 30 | version: v1 31 | kind: Pod 32 | name: foo 33 | patch: 34 | - op: replace 35 | path: /spec/containers/0/command 36 | value: 37 | - sleep 38 | - "123" 39 | strategicMergePatches: 40 | - apiVersion: v1 41 | kind: Pod 42 | metadata: 43 | name: bar 44 | spec: 45 | containers: 46 | - name: bar 47 | command: 48 | - sleep 49 | - "234" 50 | -------------------------------------------------------------------------------- /examples/helmfile/values.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: foo 6 | spec: 7 | containers: 8 | - command: 9 | - sleep 10 | - 1000 11 | image: alpine:3.9.4 12 | imagePullPolicy: IfNotPresent 13 | name: foo 14 | -------------------------------------------------------------------------------- /examples/hpa.jsonpatch.yaml: -------------------------------------------------------------------------------- 1 | target: 2 | group: autoscaling 3 | version: v2beta1 4 | kind: HorizontalPodAutoscaler 5 | name: myapp-podinfo 6 | patch: 7 | - op: replace 8 | path: /spec/maxReplicas 9 | value: 1 10 | -------------------------------------------------------------------------------- /examples/hpa.strategicmergepatch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling/v2beta1 2 | kind: HorizontalPodAutoscaler 3 | metadata: 4 | name: myapp-podinfo 5 | labels: 6 | app: podinfo 7 | chart: podinfo-2.0.1 8 | release: myapp 9 | heritage: Tiller 10 | spec: 11 | scaleTargetRef: 12 | apiVersion: apps/v1beta2 13 | kind: Deployment 14 | name: myapp-podinfo 15 | minReplicas: 3 16 | -------------------------------------------------------------------------------- /examples/kustomize/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | resources: 4 | - resources/myapp.pod.yaml 5 | - resources/mysql.pod.yaml 6 | -------------------------------------------------------------------------------- /examples/kustomize/resources/myapp.pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: myapp 5 | spec: 6 | containers: 7 | - image: myapp:1.2.3 8 | name: myapp 9 | resources: {} 10 | dnsPolicy: ClusterFirst 11 | restartPolicy: Always 12 | status: {} 13 | -------------------------------------------------------------------------------- /examples/kustomize/resources/mysql.pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: mysql 5 | spec: 6 | containers: 7 | - image: mysql:3.9 8 | name: mysql 9 | resources: {} 10 | dnsPolicy: ClusterFirst 11 | restartPolicy: Always 12 | status: {} 13 | -------------------------------------------------------------------------------- /examples/kustomize/templates/tests/test-mysql-connection.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: "{{ .Release.Name }}-test" 5 | annotations: 6 | "helm.sh/hook": test-success 7 | spec: 8 | containers: 9 | - name: {{ .Release.Name }}-credentials-test 10 | image: {{ .Values.testImage }} 11 | command: ["sh", "-c", "echo test passed!"] 12 | restartPolicy: Never 13 | -------------------------------------------------------------------------------- /examples/kustomize/values.2.yaml: -------------------------------------------------------------------------------- 1 | images: 2 | - name: mysql 3 | newName: eu.gcr.io/my-project/mysql 4 | newTag: canary 5 | - name: myapp 6 | digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3 7 | newName: my-registry/my-app 8 | 9 | namePrefix: acme- 10 | 11 | namespace: mykustomizeapp 12 | 13 | nameSuffix: -acme 14 | 15 | testImage: "alpine:3.9" 16 | -------------------------------------------------------------------------------- /examples/kustomize/values.yaml: -------------------------------------------------------------------------------- 1 | images: 2 | # helm-x automatically runs `kustomize edit set image mysql=eu.gcr.io/my-project/mysql:latest` 3 | - name: mysql 4 | newName: eu.gcr.io/my-project/mysql 5 | newTag: latest 6 | # `kustomize edit set image myapp=my-registry/my-app@sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3` 7 | - name: myapp 8 | digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3 9 | newName: my-registry/my-app 10 | 11 | # helm-x automatically runs `kustomize edit set nameprefix acme-` as... 12 | namePrefix: acme- 13 | 14 | # `kustomize edit set namespace mykustomizeapp` 15 | namespace: mykustomizeapp 16 | 17 | # helm-x automatically runs `kustomize edit set namesuffix -acme` as... 18 | nameSuffix: -acme 19 | 20 | # Used by `helm test` - See templates/test for helm tests and look for `.Values.testImage` 21 | testImage: "alpine:3.9" 22 | -------------------------------------------------------------------------------- /examples/manifests/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | creationTimestamp: null 5 | labels: 6 | run: myalpine 7 | name: myalpine 8 | spec: 9 | containers: 10 | - image: alpine:3.9 11 | name: myalpine 12 | resources: {} 13 | dnsPolicy: ClusterFirst 14 | restartPolicy: Always 15 | status: {} 16 | -------------------------------------------------------------------------------- /examples/myinject: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | cat $1 4 | echo 5 | echo \#injected 6 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mumoshu/helm-x 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/BurntSushi/toml v0.3.1 // indirect 7 | github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e // indirect 8 | github.com/Masterminds/goutils v1.1.0 // indirect 9 | github.com/Masterminds/semver v1.4.2 // indirect 10 | github.com/Masterminds/sprig v2.20.0+incompatible // indirect 11 | github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 // indirect 12 | github.com/cyphar/filepath-securejoin v0.2.2 // indirect 13 | github.com/docker/distribution v2.7.1+incompatible // indirect 14 | github.com/docker/docker v1.13.1 // indirect 15 | github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect 16 | github.com/evanphx/json-patch v4.2.0+incompatible // indirect 17 | github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect 18 | github.com/go-openapi/spec v0.19.0 // indirect 19 | github.com/gobwas/glob v0.2.3 // indirect 20 | github.com/gogo/protobuf v1.2.1 // indirect 21 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect 22 | github.com/golang/protobuf v1.3.1 23 | github.com/google/btree v1.0.0 // indirect 24 | github.com/google/gofuzz v1.0.0 // indirect 25 | github.com/google/uuid v1.1.1 // indirect 26 | github.com/googleapis/gnostic v0.2.0 // indirect 27 | github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc // indirect 28 | github.com/hashicorp/golang-lru v0.5.1 // indirect 29 | github.com/huandu/xstrings v1.2.0 // indirect 30 | github.com/imdario/mergo v0.3.7 // indirect 31 | github.com/inconshreveable/mousetrap v1.0.0 32 | github.com/json-iterator/go v1.1.6 // indirect 33 | github.com/kubernetes/cli-runtime v0.0.0-20190516231937-17bc0b7fcef5 // indirect 34 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect 35 | github.com/mitchellh/go-wordwrap v1.0.0 // indirect 36 | github.com/modern-go/reflect2 v1.0.1 // indirect 37 | github.com/opencontainers/go-digest v1.0.0-rc1 // indirect 38 | github.com/otiai10/copy v1.1.1 39 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 40 | github.com/pkg/errors v0.8.1 41 | github.com/russross/blackfriday v2.0.0+incompatible // indirect 42 | github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect 43 | github.com/spf13/cobra v0.0.3 44 | github.com/spf13/pflag v1.0.1 45 | github.com/variantdev/chartify v0.3.2 46 | golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f // indirect 47 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect 48 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect 49 | gopkg.in/inf.v0 v0.9.1 // indirect 50 | gopkg.in/square/go-jose.v2 v2.3.1 // indirect 51 | gopkg.in/yaml.v2 v2.2.2 52 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c 53 | k8s.io/api v0.0.0-20190515023547-db5a9d1c40eb 54 | k8s.io/apiextensions-apiserver v0.0.0-20190515024537-2fd0e9006049 // indirect 55 | k8s.io/apimachinery v0.0.0-20190515023456-b74e4c97951f 56 | k8s.io/apiserver v0.0.0-20190515064100-fc28ef5782df // indirect 57 | k8s.io/cli-runtime v0.0.0-20190515024640-178667528169 58 | k8s.io/client-go v11.0.0+incompatible 59 | k8s.io/helm v2.13.1+incompatible 60 | k8s.io/klog v1.0.0 61 | k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22 // indirect 62 | k8s.io/kubernetes v1.13.1 63 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 // indirect 64 | sigs.k8s.io/yaml v1.1.0 65 | vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 // indirect 66 | ) 67 | 68 | // blackfriday needed to avoid: 69 | // # k8s.io/kubernetes/pkg/kubectl/util/templates 70 | // ../go/pkg/mod/k8s.io/kubernetes@v1.13.1/pkg/kubectl/util/templates/markdown.go:30:5: cannot use &ASCIIRenderer literal (type *ASCIIRenderer) as type blackfriday.Renderer in assignment: 71 | // *ASCIIRenderer does not implement blackfriday.Renderer (missing RenderFooter method) 72 | // ../go/pkg/mod/k8s.io/kubernetes@v1.13.1/pkg/kubectl/util/templates/markdown.go:64:11: undefined: blackfriday.LIST_ITEM_BEGINNING_OF_LIST 73 | // ../go/pkg/mod/k8s.io/kubernetes@v1.13.1/pkg/kubectl/util/templates/markdown.go:71:11: undefined: blackfriday.LIST_TYPE_ORDERED 74 | // ../go/pkg/mod/k8s.io/kubernetes@v1.13.1/pkg/kubectl/util/templates/normalizers.go:73:35: too many arguments to conversion to blackfriday.Markdown: blackfriday.Markdown(bytes, composite literal, 0) 75 | 76 | replace github.com/russross/blackfriday => github.com/russross/blackfriday v1.5.2 77 | 78 | replace k8s.io/client-go => k8s.io/client-go v0.0.0-20181213151034-8d9ed539ba31 79 | 80 | replace k8s.io/api => k8s.io/api v0.0.0-20181213150558-05914d821849 81 | 82 | replace k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20181213153335-0fe22c71c476 83 | 84 | replace k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93 85 | 86 | replace k8s.io/apiserver => k8s.io/apiserver v0.0.0-20181213151703-3ccfe8365421 87 | 88 | replace k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20181213153952-835b10687cb6 89 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 3 | github.com/Azure/go-autorest v11.1.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 4 | github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 5 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 6 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 7 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 8 | github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e h1:eb0Pzkt15Bm7f2FFYv7sjY7NPFi3cPkS3tv1CcrFBWA= 9 | github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= 10 | github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= 11 | github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 12 | github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= 13 | github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 14 | github.com/Masterminds/sprig v2.20.0+incompatible h1:dJTKKuUkYW3RMFdQFXPU/s6hg10RgctmTjRcbZ98Ap8= 15 | github.com/Masterminds/sprig v2.20.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= 16 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 17 | github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 18 | github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= 19 | github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 20 | github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 21 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= 22 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 23 | github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 24 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 25 | github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 26 | github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 h1:HD4PLRzjuCVW79mQ0/pdsalOLHJ+FaEoqJLxfltpb2U= 27 | github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= 28 | github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 29 | github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 30 | github.com/coreos/go-oidc v0.0.0-20180117170138-065b426bd416/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= 31 | github.com/coreos/go-semver v0.0.0-20180108230905-e214231b295a/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 32 | github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 33 | github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 34 | github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= 35 | github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= 36 | github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 37 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 38 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 39 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 40 | github.com/dgrijalva/jwt-go v0.0.0-20160705203006-01aeca54ebda/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 41 | github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= 42 | github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 43 | github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 44 | github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= 45 | github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 46 | github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 47 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= 48 | github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 49 | github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= 50 | github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= 51 | github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 52 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= 53 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 54 | github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550 h1:mV9jbLoSW/8m4VK16ZkHTozJa8sesK5u5kTMFysTYac= 55 | github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 56 | github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I= 57 | github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 58 | github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= 59 | github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= 60 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 61 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 h1:ZktWZesgun21uEDrwW7iEV1zPCGQldM2atlJZ3TdvVM= 62 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 63 | github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4 h1:bRzFpEzvausOAt4va+I/22BZ1vXDtERngp0BNYDKej0= 64 | github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 65 | github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 66 | github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= 67 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 68 | github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= 69 | github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 70 | github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= 71 | github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 72 | github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= 73 | github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= 74 | github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= 75 | github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 76 | github.com/go-openapi/jsonpointer v0.19.0 h1:FTUMcX77w5rQkClIzDtTxvn6Bsa894CcrzNj2MMfeg8= 77 | github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= 78 | github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= 79 | github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA= 80 | github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 81 | github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk= 82 | github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= 83 | github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 84 | github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= 85 | github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= 86 | github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q= 87 | github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= 88 | github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 89 | github.com/go-openapi/spec v0.17.2 h1:eb2NbuCnoe8cWAxhtK6CfMWUYmiFEZJ9Hx3Z2WRwJ5M= 90 | github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 91 | github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= 92 | github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= 93 | github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= 94 | github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= 95 | github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= 96 | github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 97 | github.com/go-openapi/swag v0.17.2 h1:K/ycE/XTUDFltNHSO32cGRUhrVGJD64o8WgAIZNyc3k= 98 | github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= 99 | github.com/go-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= 100 | github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= 101 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= 102 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= 103 | github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415 h1:WSBJMqJbLxsn+bTCPyPYZfqHdJmc8MK4wrBjMft6BAM= 104 | github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 105 | github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= 106 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 107 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 108 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= 109 | github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 110 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= 111 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 112 | github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 113 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 114 | github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= 115 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 116 | github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 117 | github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= 118 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 119 | github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 120 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 121 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 122 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 123 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= 124 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 125 | github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= 126 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 127 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 128 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 129 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 130 | github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 131 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k= 132 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 133 | github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= 134 | github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= 135 | github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= 136 | github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 137 | github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 138 | github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc h1:f8eY6cV/x1x+HLjOp4r72s/31/V2aTUtg5oKRRPf8/Q= 139 | github.com/gregjones/httpcache v0.0.0-20190212212710-3befbb6ad0cc/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 140 | github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 141 | github.com/grpc-ecosystem/go-grpc-prometheus v0.0.0-20170330212424-2500245aa611/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 142 | github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= 143 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 144 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 145 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 146 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 147 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 148 | github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= 149 | github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= 150 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 151 | github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= 152 | github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 153 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 154 | github.com/jonboulle/clockwork v0.0.0-20141017032234-72f9bd7c4e0c/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 155 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 156 | github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be h1:AHimNtVIpiBjPUhEF5KNCkrUyqTSA5zWUl8sQ2bfGBE= 157 | github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 158 | github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= 159 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 160 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 161 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 162 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 163 | github.com/kubernetes/cli-runtime v0.0.0-20190515024640-178667528169 h1:dHnuUa4nmdg8Vb0AMZIH44eyBvHREgLbptLuRy4jFsI= 164 | github.com/kubernetes/cli-runtime v0.0.0-20190516231937-17bc0b7fcef5 h1:nU71QL17PgsIBxFCfd/9NuTJj79nn5krw/+1iCmVgWs= 165 | github.com/kubernetes/cli-runtime v0.0.0-20190516231937-17bc0b7fcef5/go.mod h1:yeYmJwRRBi9I/csnH+wpam2v99PgDhwonYwOAiklvaw= 166 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= 167 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= 168 | github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 169 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= 170 | github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 171 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 172 | github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= 173 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= 174 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 175 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 176 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 177 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 178 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 179 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 180 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 181 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 182 | github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= 183 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 184 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 185 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 186 | github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 187 | github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= 188 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 189 | github.com/otiai10/copy v0.0.0-20180813032824-7e9a647135a1 h1:A7kMXwDPBTfIVRv2l6XV3U6Su3SzLUzZjxnDDQVZDIY= 190 | github.com/otiai10/copy v0.0.0-20180813032824-7e9a647135a1/go.mod h1:pXzZSDlN+HPzSdyIBnKNN9ptD9Hx7iZMWIJPTwo4FPE= 191 | github.com/otiai10/copy v1.1.1 h1:PH7IFlRQ6Fv9vYmuXbDRLdgTHoP1w483kPNUP2bskpo= 192 | github.com/otiai10/copy v1.1.1/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= 193 | github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= 194 | github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= 195 | github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= 196 | github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= 197 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= 198 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 199 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 200 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 201 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 202 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 203 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 204 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 205 | github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= 206 | github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= 207 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 208 | github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 209 | github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 210 | github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= 211 | github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= 212 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 213 | github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk= 214 | github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 215 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 216 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 217 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 218 | github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 219 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 220 | github.com/spf13/cobra v0.0.0-20180319062004-c439c4fa0937/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 221 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= 222 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 223 | github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 224 | github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= 225 | github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 226 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 227 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 228 | github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 229 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 230 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 231 | github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 232 | github.com/variantdev/chartify v0.0.0-20200330123007-ddc79388188c h1:PiAOZUdKtf8tIfps1fLaEsP9BCTT8hR9FawTfAuLY5o= 233 | github.com/variantdev/chartify v0.0.0-20200330123007-ddc79388188c/go.mod h1:DzFQ1XeNBpbLdbze39ai3auS4b+Xo/KzRluEY/LyqlM= 234 | github.com/variantdev/chartify v0.3.0 h1:vm9cY+Amj44g7scz/FVpa3s13XqzFvTJrdM6iufiKEM= 235 | github.com/variantdev/chartify v0.3.0/go.mod h1:0tw+4doFHsNnhttYx7I9Pv/dsZ82BD4UuTV9saBOcfw= 236 | github.com/variantdev/chartify v0.3.2 h1:EjwlY/ArgkOhsvbMG+xyeBnsWRV6eaxfHfJj9st8PvI= 237 | github.com/variantdev/chartify v0.3.2/go.mod h1:0tw+4doFHsNnhttYx7I9Pv/dsZ82BD4UuTV9saBOcfw= 238 | github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 239 | go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 240 | go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 241 | go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 242 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 243 | golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 244 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 245 | golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f h1:R423Cnkcp5JABoeemiGEPlt9tHXFfw5kvc0yqlxRPWo= 246 | golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 247 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 248 | golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 249 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 250 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 251 | golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 252 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 253 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 254 | golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 255 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 256 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 257 | golang.org/x/net v0.0.0-20190206173232-65e2d4e15006 h1:bfLnR+k0tq5Lqt6dflRLcZiz6UaXCMt3vhYJ1l4FQ80= 258 | golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 259 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 260 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= 261 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 262 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA= 263 | golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 264 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 265 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 266 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 267 | golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 268 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 269 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 270 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 271 | golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc= 272 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 273 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= 274 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 275 | golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 276 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 277 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 278 | golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU= 279 | golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 280 | golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 281 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= 282 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 283 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 284 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 285 | golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 286 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 287 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 288 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 289 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 290 | gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= 291 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 292 | gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= 293 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 294 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 295 | google.golang.org/genproto v0.0.0-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 296 | google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 297 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 298 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 299 | gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o= 300 | gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 301 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 302 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 303 | gopkg.in/natefinch/lumberjack.v2 v2.0.0-20150622162204-20b71e5b60d7/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 304 | gopkg.in/square/go-jose.v2 v2.0.0-20180411045311-89060dee6a84/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 305 | gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= 306 | gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= 307 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 308 | gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= 309 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 310 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 311 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 312 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 313 | gopkg.in/yaml.v3 v3.0.0-20190409140830-cdc409dda467 h1:w3VhdSYz2sIVz54Ta/eDCCfCQ4fQkDgRxMACggArIUw= 314 | gopkg.in/yaml.v3 v3.0.0-20190409140830-cdc409dda467/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 315 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 316 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 317 | gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= 318 | k8s.io/api v0.0.0-20181213150558-05914d821849 h1:WZFcFPXmLR7g5CxQNmjWv0mg8qulJLxDghbzS4pQtzY= 319 | k8s.io/api v0.0.0-20181213150558-05914d821849/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 320 | k8s.io/api v0.0.0-20190503110853-61630f889b3c h1:y1nbvZVlOyUa+p4RVXqQj+s6W+FjZZNVkgG5pvYpFhU= 321 | k8s.io/api v0.0.0-20190503110853-61630f889b3c/go.mod h1:42M1T54fVvXj2R/yqB+v9ksH4xI41q6XU/NUlo3hyjk= 322 | k8s.io/api v0.0.0-20190515023547-db5a9d1c40eb h1:z1fFVKHVQNtGcAPbYljoW2rZT+0ITuj99cmGH9RBrWE= 323 | k8s.io/api v0.0.0-20190515023547-db5a9d1c40eb/go.mod h1:fbdFiGtx7GQ3+vkBAYto3QsSImiYIJdpH3YfaclST/U= 324 | k8s.io/apiextensions-apiserver v0.0.0-20181213153335-0fe22c71c476 h1:Ws9zfxsgV19Durts9ftyTG7TO0A/QLhmu98VqNWLiH8= 325 | k8s.io/apiextensions-apiserver v0.0.0-20181213153335-0fe22c71c476/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= 326 | k8s.io/apiextensions-apiserver v0.0.0-20190515024537-2fd0e9006049 h1:nVYyNZl5lEeojOejb0pSiVVNx8zsorGXggeQmRRjWFM= 327 | k8s.io/apiextensions-apiserver v0.0.0-20190515024537-2fd0e9006049/go.mod h1:yMQkVi5Qu0vmH4rNmnVu6v5kf1GnPj8+6TJG+he4+kw= 328 | k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93 h1:tT6oQBi0qwLbbZSfDkdIsb23EwaLY85hoAV4SpXfdao= 329 | k8s.io/apimachinery v0.0.0-20181127025237-2b1284ed4c93/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 330 | k8s.io/apimachinery v0.0.0-20190502092502-a44ef629a3c9/go.mod h1:5CBnzrKYGHzv9ZsSKmQ8wHt4XI4/TUBPDwYM9FlZMyw= 331 | k8s.io/apimachinery v0.0.0-20190503221204-7a17edec881a h1:dJykmqZaQ9hZACn0wQHt+OW2W0NN2tAGEc8rfT8JDR0= 332 | k8s.io/apimachinery v0.0.0-20190503221204-7a17edec881a/go.mod h1:5CBnzrKYGHzv9ZsSKmQ8wHt4XI4/TUBPDwYM9FlZMyw= 333 | k8s.io/apimachinery v0.0.0-20190515023456-b74e4c97951f h1:cBrF1gFrJrvimOHZzyEHrvtlfqPV+KM7QZt3M0mepEg= 334 | k8s.io/apimachinery v0.0.0-20190515023456-b74e4c97951f/go.mod h1:Ew3b/24/JSgJdn4RsnrLskv3LvMZDlZ1Fl1xopsJftY= 335 | k8s.io/apiserver v0.0.0-20181213151703-3ccfe8365421 h1:NyOpnIh+7SLvC05NGCIXF9c4KhnkTZQE2SxF+m9otww= 336 | k8s.io/apiserver v0.0.0-20181213151703-3ccfe8365421/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w= 337 | k8s.io/apiserver v0.0.0-20190515024203-a3c8296cef8c/go.mod h1:c9qwKQexUHQq1ALDC7vAgSXOVXAFkGil5ZQWdqr3RhI= 338 | k8s.io/apiserver v0.0.0-20190515064100-fc28ef5782df h1:MHj++nRr6lfz8YbpbERsq4TnA8aSH20LJA73E7CF/N0= 339 | k8s.io/apiserver v0.0.0-20190515064100-fc28ef5782df/go.mod h1:Cb9RYDkm/hllXjb8VMbwWwjPQMRRFB6y+5MF+MvUWno= 340 | k8s.io/cli-runtime v0.0.0-20181213153952-835b10687cb6 h1:VCcOQ34dGCCgCCJIXgYGeqvr+X3qaMdfC+EYCsQoTTU= 341 | k8s.io/cli-runtime v0.0.0-20181213153952-835b10687cb6/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM= 342 | k8s.io/cli-runtime v0.0.0-20190515024640-178667528169 h1:TsdAjHaNEx9MvuyK3y40PZfG0ksgXMehiXFd2frsXxY= 343 | k8s.io/cli-runtime v0.0.0-20190515024640-178667528169/go.mod h1:GSg3c32GxsJUX6CEyfZTn6ihSLd35org5Qksd1jW7AY= 344 | k8s.io/cli-runtime v0.0.0-20190516231937-17bc0b7fcef5 h1:kSosNyQ/BwwdA3MsCX8wIhdELKQ0P4c1BVkL4LC3HAI= 345 | k8s.io/cli-runtime v0.0.0-20190516231937-17bc0b7fcef5/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM= 346 | k8s.io/client-go v0.0.0-20181213151034-8d9ed539ba31 h1:OH3z6khCtxnJBAc0C5CMYWLl1CoK5R5fngX7wrwdN5c= 347 | k8s.io/client-go v0.0.0-20181213151034-8d9ed539ba31/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= 348 | k8s.io/client-go v0.0.0-20190515023709-78e94f51a042/go.mod h1:Ucfy225uJpWBtWGDwTtqUZmmgR/AzYM0vge2iB/bTQ4= 349 | k8s.io/client-go v0.0.0-20190515063710-7b18d6600f6b/go.mod h1:Ucfy225uJpWBtWGDwTtqUZmmgR/AzYM0vge2iB/bTQ4= 350 | k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= 351 | k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= 352 | k8s.io/cloud-provider v0.0.0-20190515224602-984e89dd9575 h1:CAoSbdSP0jZUeXb1ylR3iGok9DvyJs8QB+65XTElWI4= 353 | k8s.io/code-generator v0.0.0-20190511023357-639c964206c2/go.mod h1:YMQ7Lt97nW/I6nHACDccgS/sPAyrHQNans96RwPaSb8= 354 | k8s.io/component-base v0.0.0-20190515024022-2354f2393ad4 h1:TOebDR8jh/AcqOEWnotJ+DYVUNcMA1GT86TR4Bg70KQ= 355 | k8s.io/component-base v0.0.0-20190515024022-2354f2393ad4/go.mod h1:pKRi1i5IQdJDpK1LItot8oy27Bc3zL/hQiy9T171rvE= 356 | k8s.io/gengo v0.0.0-20190116091435-f8a0810f38af/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 357 | k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= 358 | k8s.io/helm v2.13.1+incompatible h1:qt0LBsHQ7uxCtS3F2r3XI0DNm8ml0xQeSJixUorDyn0= 359 | k8s.io/helm v2.13.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= 360 | k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 361 | k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= 362 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 363 | k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= 364 | k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= 365 | k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30 h1:TRb4wNWoBVrH9plmkp2q86FIDppkbrEXdXlxU3a3BMI= 366 | k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 367 | k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22 h1:f0BTap/vrgs21vVbJ1ySdsNtcivpA1x4ut6Wla9HKKw= 368 | k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22/go.mod h1:iU+ZGYsNlvU9XKUSso6SQfKTCCw7lFduMZy26Mgr2Fw= 369 | k8s.io/kubernetes v1.13.1 h1:IwCCcPOZwY9rKcQyBJYXAE4Wgma4oOW5NYR3HXKFfZ8= 370 | k8s.io/kubernetes v1.13.1/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= 371 | k8s.io/kubernetes v1.14.2 h1:Gdq2hPpttbaJBoClIanCE6WSu4IZReA54yhkZtvPUOo= 372 | k8s.io/kubernetes v1.14.2/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= 373 | k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= 374 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 h1:VBM/0P5TWxwk+Nw6Z+lAw3DKgO76g90ETOiA6rfLV1Y= 375 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 376 | modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw= 377 | modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk= 378 | modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= 379 | modernc.org/strutil v1.0.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= 380 | modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= 381 | sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= 382 | sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= 383 | sigs.k8s.io/structured-merge-diff v0.0.0-20190302045857-e85c7b244fd2/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 384 | sigs.k8s.io/structured-merge-diff v0.0.0-20190426204423-ea680f03cc65/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= 385 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 386 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 387 | vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787 h1:O69FD9pJA4WUZlEwYatBEEkRWKQ5cKodWpdKTrCS/iQ= 388 | vbom.ml/util v0.0.0-20180919145318-efcd4e0f9787/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= 389 | -------------------------------------------------------------------------------- /hack/semtag: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PROG=semtag 4 | PROG_VERSION="v0.1.0" 5 | 6 | SEMVER_REGEX="^v?(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(\-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$" 7 | IDENTIFIER_REGEX="^\-([0-9A-Za-z-]+)\.([0-9A-Za-z-]+)*$" 8 | 9 | # Global variables 10 | FIRST_VERSION="v0.0.0" 11 | finalversion=$FIRST_VERSION 12 | lastversion=$FIRST_VERSION 13 | hasversiontag="false" 14 | scope="patch" 15 | displayonly="false" 16 | forcetag="false" 17 | forcedversion= 18 | versionname= 19 | identifier= 20 | 21 | HELP="\ 22 | Usage: 23 | $PROG 24 | $PROG getlast 25 | $PROG getfinal 26 | $PROG (final|alpha|beta|candidate) [-s (major|minor|patch|auto) | -o] 27 | $PROG --help 28 | $PROG --version 29 | Options: 30 | -s The scope that must be increased, can be major, minor or patch. 31 | The resulting version will match X.Y.Z(-PRERELEASE)(+BUILD) 32 | where X, Y and Z are positive integers, PRERELEASE is an optionnal 33 | string composed of alphanumeric characters describing if the build is 34 | a release candidate, alpha or beta version, with a number. 35 | BUILD is also an optional string composed of alphanumeric 36 | characters and hyphens. 37 | Setting the scope as 'auto', the script will chose the scope between 38 | 'minor' and 'patch', depending on the amount of lines added (<10% will 39 | choose patch). 40 | -v Specifies manually the version to be tagged, must be a valid semantic version 41 | in the format X.Y.Z where X, Y and Z are positive integers. 42 | -o Output the version only, shows the bumped version, but doesn't tag. 43 | -f Forces to tag, even if there are unstaged or uncommited changes. 44 | Commands: 45 | --help Print this help message. 46 | --version Prints the program's version. 47 | get Returns both current final version and last tagged version. 48 | getlast Returns the latest tagged version. 49 | getfinal Returns the latest tagged final version. 50 | getcurrent Returns the current version, based on the latest one, if there are uncommited or 51 | unstaged changes, they will be reflected in the version, adding the number of 52 | pending commits, current branch and commit hash. 53 | final Tags the current build as a final version, this only can be done on the master branch. 54 | candidate Tags the current build as a release candidate, the tag will contain all 55 | the commits from the last final version. 56 | alpha Tags the current build as an alpha version, the tag will contain all 57 | the commits from the last final version. 58 | beta Tags the current build as a beta version, the tag will contain all 59 | the commits from the last final version." 60 | 61 | # Commands and options 62 | ACTION="getlast" 63 | ACTION="$1" 64 | shift 65 | 66 | # We get the parameters 67 | while getopts "v:s:of" opt; do 68 | case $opt in 69 | v) 70 | forcedversion="$OPTARG" 71 | ;; 72 | s) 73 | scope="$OPTARG" 74 | ;; 75 | o) 76 | displayonly="true" 77 | ;; 78 | f) 79 | forcetag="true" 80 | ;; 81 | \?) 82 | echo "Invalid option: -$OPTARG" >&2 83 | exit 1 84 | ;; 85 | :) 86 | echo "Option -$OPTARG requires an argument." >&2 87 | exit 1 88 | ;; 89 | esac 90 | done 91 | 92 | # Gets a string with the version and returns an array of maximum size of 5 with all the parts of the sematinc version 93 | # $1 The string containing the version in semantic format 94 | # $2 The variable to store the result array: 95 | # position 0: major number 96 | # position 1: minor number 97 | # position 2: patch number 98 | # position 3: identifier (or prerelease identifier) 99 | # position 4: build info 100 | function explode_version { 101 | local __version=$1 102 | local __result=$2 103 | if [[ $__version =~ $SEMVER_REGEX ]] ; then 104 | local __major=${BASH_REMATCH[1]} 105 | local __minor=${BASH_REMATCH[2]} 106 | local __patch=${BASH_REMATCH[3]} 107 | local __prere=${BASH_REMATCH[4]} 108 | local __build=${BASH_REMATCH[5]} 109 | eval "$__result=(\"$__major\" \"$__minor\" \"$__patch\" \"$__prere\" \"$__build\")" 110 | else 111 | eval "$__result=" 112 | fi 113 | } 114 | 115 | # Compare two versions and returns -1, 0 or 1 116 | # $1 The first version to compare 117 | # $2 The second version to compare 118 | # $3 The variable where to store the result 119 | function compare_versions { 120 | local __first 121 | local __second 122 | explode_version $1 __first 123 | explode_version $2 __second 124 | local lv=$3 125 | 126 | # Compares MAJOR, MINOR and PATCH 127 | for i in 0 1 2; do 128 | local __numberfirst=${__first[$i]} 129 | local __numbersecond=${__second[$i]} 130 | case $(($__numberfirst - $__numbersecond)) in 131 | 0) 132 | ;; 133 | -[0-9]*) 134 | eval "$lv=-1" 135 | return 0 136 | ;; 137 | [0-9]*) 138 | eval "$lv=1" 139 | return 0 140 | ;; 141 | esac 142 | done 143 | 144 | # Identifiers should compare with the ASCII order. 145 | local __identifierfirst=${__first[3]} 146 | local __identifiersecond=${__second[3]} 147 | if [[ -n "$__identifierfirst" ]] && [[ -n "$__identifiersecond" ]]; then 148 | if [[ "$__identifierfirst" > "$__identifiersecond" ]]; then 149 | eval "$lv=1" 150 | return 0 151 | elif [[ "$__identifierfirst" < "$__identifiersecond" ]]; then 152 | eval "$lv=-1" 153 | return 0 154 | fi 155 | elif [[ -z "$__identifierfirst" ]] && [[ -n "$__identifiersecond" ]]; then 156 | eval "$lv=1" 157 | return 0 158 | elif [[ -n "$__identifierfirst" ]] && [[ -z "$__identifiersecond" ]]; then 159 | eval "$lv=-1" 160 | return 0 161 | fi 162 | 163 | eval "$lv=0" 164 | } 165 | 166 | # Returns the last version of two 167 | # $1 The first version to compare 168 | # $2 The second version to compare 169 | # $3 The variable where to store the last one 170 | function get_latest_of_two { 171 | local __first=$1 172 | local __second=$2 173 | local __result 174 | local __latest=$3 175 | compare_versions $__first $__second __result 176 | case $__result in 177 | 0) 178 | eval "$__latest=$__second" 179 | ;; 180 | -1) 181 | eval "$__latest=$__second" 182 | ;; 183 | 1) 184 | eval "$__latest=$__first" 185 | ;; 186 | esac 187 | } 188 | 189 | # Assigns a 2 size array with the identifier, having the identifier at pos 0, and the number in pos 1 190 | # $1 The identifier in the format -id.# 191 | # $2 The vferiable where to store the 2 size array 192 | function explode_identifier { 193 | local __identifier=$1 194 | local __result=$2 195 | if [[ $__identifier =~ $IDENTIFIER_REGEX ]] ; then 196 | local __id=${BASH_REMATCH[1]} 197 | local __number=${BASH_REMATCH[2]} 198 | if [[ -z "$__number" ]]; then 199 | __number=1 200 | fi 201 | eval "$__result=(\"$__id\" \"$__number\")" 202 | else 203 | eval "$__result=" 204 | fi 205 | } 206 | 207 | # Gets a list of tags and assigns the base and latest versions 208 | # Receives an array with the tags containing the versions 209 | # Assigns to the global variables finalversion and lastversion the final version and the latest version 210 | function get_latest { 211 | local __taglist=("$@") 212 | local __tagsnumber=${#__taglist[@]} 213 | local __current 214 | case $__tagsnumber in 215 | 0) 216 | finalversion=$FIRST_VERSION 217 | lastversion=$FIRST_VERSION 218 | ;; 219 | 1) 220 | __current=${__taglist[0]} 221 | explode_version $__current ver 222 | if [ -n "$ver" ]; then 223 | if [ -n "${ver[3]}" ]; then 224 | finalversion=$FIRST_VERSION 225 | else 226 | finalversion=$__current 227 | fi 228 | lastversion=$__current 229 | else 230 | finalversion=$FIRST_VERSION 231 | lastversion=$FIRST_VERSION 232 | fi 233 | ;; 234 | *) 235 | local __lastpos=$(($__tagsnumber-1)) 236 | for i in $(seq 0 $__lastpos) 237 | do 238 | __current=${__taglist[i]} 239 | explode_version ${__taglist[i]} ver 240 | if [ -n "$ver" ]; then 241 | if [ -z "${ver[3]}" ]; then 242 | get_latest_of_two $finalversion $__current finalversion 243 | get_latest_of_two $lastversion $finalversion lastversion 244 | else 245 | get_latest_of_two $lastversion $__current lastversion 246 | fi 247 | fi 248 | done 249 | ;; 250 | esac 251 | 252 | if git rev-parse -q --verify "refs/tags/$lastversion" >/dev/null; then 253 | hasversiontag="true" 254 | else 255 | hasversiontag="false" 256 | fi 257 | } 258 | 259 | # Gets the next version given the provided scope 260 | # $1 The version that is going to be bumped 261 | # $2 The scope to bump 262 | # $3 The variable where to stoer the result 263 | function get_next_version { 264 | local __exploded 265 | local __fromversion=$1 266 | local __scope=$2 267 | local __result=$3 268 | explode_version $__fromversion __exploded 269 | case $__scope in 270 | major) 271 | __exploded[0]=$((${__exploded[0]}+1)) 272 | __exploded[1]=0 273 | __exploded[2]=0 274 | ;; 275 | minor) 276 | __exploded[1]=$((${__exploded[1]}+1)) 277 | __exploded[2]=0 278 | ;; 279 | patch) 280 | __exploded[2]=$((${__exploded[2]}+1)) 281 | ;; 282 | esac 283 | 284 | eval "$__result=v${__exploded[0]}.${__exploded[1]}.${__exploded[2]}" 285 | } 286 | 287 | function bump_version { 288 | ## First we try to get the next version based on the existing last one 289 | if [ "$scope" == "auto" ]; then 290 | get_scope_auto scope 291 | fi 292 | 293 | local __candidatefromlast=$FIRST_VERSION 294 | local __explodedlast 295 | explode_version $lastversion __explodedlast 296 | if [[ -n "${__explodedlast[3]}" ]]; then 297 | # Last version is not final 298 | local __idlast 299 | explode_identifier ${__explodedlast[3]} __idlast 300 | 301 | # We get the last, given the desired id based on the scope 302 | __candidatefromlast="v${__explodedlast[0]}.${__explodedlast[1]}.${__explodedlast[2]}" 303 | if [[ -n "$identifier" ]]; then 304 | local __nextid="$identifier.1" 305 | if [ "$identifier" == "${__idlast[0]}" ]; then 306 | # We target the same identifier as the last so we increase one 307 | __nextid="$identifier.$(( ${__idlast[1]}+1 ))" 308 | __candidatefromlast="$__candidatefromlast-$__nextid" 309 | else 310 | # Different identifiers, we make sure we are assigning a higher identifier, if not, we increase the version 311 | __candidatefromlast="$__candidatefromlast-$__nextid" 312 | local __comparedwithlast 313 | compare_versions $__candidatefromlast $lastversion __comparedwithlast 314 | if [ "$__comparedwithlast" == -1 ]; then 315 | get_next_version $__candidatefromlast $scope __candidatefromlast 316 | __candidatefromlast="$__candidatefromlast-$__nextid" 317 | fi 318 | fi 319 | fi 320 | fi 321 | 322 | # Then we try to get the version based on the latest final one 323 | local __candidatefromfinal=$FIRST_VERSION 324 | get_next_version $finalversion $scope __candidatefromfinal 325 | if [[ -n "$identifier" ]]; then 326 | __candidatefromfinal="$__candidatefromfinal-$identifier.1" 327 | fi 328 | 329 | # Finally we compare both candidates 330 | local __resultversion 331 | local __result 332 | compare_versions $__candidatefromlast $__candidatefromfinal __result 333 | case $__result in 334 | 0) 335 | __resultversion=$__candidatefromlast 336 | ;; 337 | -1) 338 | __resultversion="$__candidatefromfinal" 339 | ;; 340 | 1) 341 | __resultversion=$__candidatefromlast 342 | ;; 343 | esac 344 | 345 | eval "$1=$__resultversion" 346 | } 347 | 348 | function increase_version { 349 | local __version= 350 | 351 | if [ -z $forcedversion ]; then 352 | bump_version __version 353 | else 354 | if [[ $forcedversion =~ $SEMVER_REGEX ]] ; then 355 | compare_versions $forcedversion $lastversion __result 356 | if [ $__result -le 0 ]; then 357 | echo "Version can't be lower than last version: $lastversion" 358 | exit 1 359 | fi 360 | else 361 | echo "Non valid version to bump" 362 | exit 1 363 | fi 364 | __version=$forcedversion 365 | fi 366 | 367 | if [ "$displayonly" == "true" ]; then 368 | echo "$__version" 369 | else 370 | if [ "$forcetag" == "false" ]; then 371 | check_git_dirty_status 372 | fi 373 | local __commitlist 374 | if [ "$finalversion" == "$FIRST_VERSION" ] || [ "$hasversiontag" != "true" ]; then 375 | __commitlist="$(git log --pretty=oneline | cat)" 376 | else 377 | __commitlist="$(git log --pretty=oneline $finalversion... | cat)" 378 | fi 379 | 380 | # If we are forcing a bump, we add bump to the commit list 381 | if [[ -z $__commitlist && "$forcetag" == "true" ]]; then 382 | __commitlist="bump" 383 | fi 384 | 385 | if [[ -z $__commitlist ]]; then 386 | echo "No commits since the last final version, not bumping version" 387 | else 388 | if [[ -z $versionname ]]; then 389 | versionname=$(date -u +"%Y-%m-%dT%H:%M:%SZ") 390 | fi 391 | local __message="$versionname 392 | $__commitlist" 393 | 394 | # We check we have info on the user 395 | local __username=$(git config user.name) 396 | if [ -z "$__username" ]; then 397 | __username=$(id -u -n) 398 | git config user.name $__username 399 | fi 400 | local __useremail=$(git config user.email) 401 | if [ -z "$__useremail" ]; then 402 | __useremail=$(hostname) 403 | git config user.email "$__username@$__useremail" 404 | fi 405 | 406 | git tag -a $__version -m "$__message" 407 | 408 | # If we have a remote, we push there 409 | local __remote=$(git remote) 410 | if [[ -n $__remote ]]; then 411 | git push $__remote $__version > /dev/null 412 | if [ $? -eq 0 ]; then 413 | echo "$__version" 414 | else 415 | echo "Error pushing the tag $__version to $__remote" 416 | exit 1 417 | fi 418 | else 419 | echo "$__version" 420 | fi 421 | fi 422 | fi 423 | } 424 | 425 | function check_git_dirty_status { 426 | local __repostatus= 427 | get_work_tree_status __repostatus 428 | 429 | if [ "$__repostatus" == "uncommitted" ]; then 430 | echo "ERROR: You have uncommitted changes" 431 | git status --porcelain 432 | exit 1 433 | fi 434 | 435 | if [ "$__repostatus" == "unstaged" ]; then 436 | echo "ERROR: You have unstaged changes" 437 | git status --porcelain 438 | exit 1 439 | fi 440 | } 441 | 442 | # Get the total amount of lines of code in the repo 443 | function get_total_lines { 444 | local __empty_id="$(git hash-object -t tree /dev/null)" 445 | local __changes="$(git diff --numstat $__empty_id | cat)" 446 | local __added_deleted=$1 447 | get_changed_lines "$__changes" $__added_deleted 448 | } 449 | 450 | # Get the total amount of lines of code since the provided tag 451 | function get_sincetag_lines { 452 | local __sincetag=$1 453 | local __changes="$(git diff --numstat $__sincetag | cat)" 454 | local __added_deleted=$2 455 | get_changed_lines "$__changes" $__added_deleted 456 | } 457 | 458 | function get_changed_lines { 459 | local __changes_numstat=$1 460 | local __result=$2 461 | IFS=$'\n' read -rd '' -a __changes_array <<<"$__changes_numstat" 462 | local __diff_regex="^([0-9]+)[[:space:]]+([0-9]+)[[:space:]]+.+$" 463 | 464 | local __total_added=0 465 | local __total_deleted=0 466 | for i in "${__changes_array[@]}" 467 | do 468 | if [[ $i =~ $__diff_regex ]] ; then 469 | local __added=${BASH_REMATCH[1]} 470 | local __deleted=${BASH_REMATCH[2]} 471 | __total_added=$(( $__total_added+$__added )) 472 | __total_deleted=$(( $__total_deleted+$__deleted )) 473 | fi 474 | done 475 | eval "$2=( $__total_added $__total_deleted )" 476 | } 477 | 478 | function get_scope_auto { 479 | local __verbose=$2 480 | local __total=0 481 | local __since=0 482 | local __scope= 483 | 484 | get_total_lines __total 485 | get_sincetag_lines $finalversion __since 486 | 487 | local __percentage=0 488 | if [ "$__total" != "0" ]; then 489 | local __percentage=$(( 100*$__since/$__total )) 490 | if [ $__percentage -gt "10" ]; then 491 | __scope="minor" 492 | else 493 | __scope="patch" 494 | fi 495 | fi 496 | 497 | eval "$1=$__scope" 498 | if [[ -n "$__verbose" ]]; then 499 | echo "[Auto Scope] Percentage of lines changed: $__percentage" 500 | echo "[Auto Scope] : $__scope" 501 | fi 502 | } 503 | 504 | function get_work_tree_status { 505 | # Update the index 506 | git update-index -q --ignore-submodules --refresh > /dev/null 507 | eval "$1=" 508 | 509 | if ! git diff-files --quiet --ignore-submodules -- > /dev/null 510 | then 511 | eval "$1=unstaged" 512 | fi 513 | 514 | if ! git diff-index --cached --quiet HEAD --ignore-submodules -- > /dev/null 515 | then 516 | eval "$1=uncommitted" 517 | fi 518 | } 519 | 520 | function get_current { 521 | if [ "$hasversiontag" == "true" ]; then 522 | local __commitcount="$(git rev-list $lastversion.. --count)" 523 | else 524 | local __commitcount="$(git rev-list --count HEAD)" 525 | fi 526 | local __status= 527 | get_work_tree_status __status 528 | 529 | if [ "$__commitcount" == "0" ] && [ -z "$__status" ]; then 530 | eval "$1=$lastversion" 531 | else 532 | local __buildinfo="$(git rev-parse --short HEAD)" 533 | local __currentbranch="$(git rev-parse --abbrev-ref HEAD)" 534 | if [ "$__currentbranch" != "master" ]; then 535 | __buildinfo="$__currentbranch.$__buildinfo" 536 | fi 537 | 538 | local __suffix= 539 | if [ "$__commitcount" != "0" ]; then 540 | if [ -n "$__suffix" ]; then 541 | __suffix="$__suffix." 542 | fi 543 | __suffix="$__suffix$__commitcount" 544 | fi 545 | if [ -n "$__status" ]; then 546 | if [ -n "$__suffix" ]; then 547 | __suffix="$__suffix." 548 | fi 549 | __suffix="$__suffix$__status" 550 | fi 551 | 552 | __suffix="$__suffix+$__buildinfo" 553 | if [ "$lastversion" == "$finalversion" ]; then 554 | scope="patch" 555 | identifier= 556 | local __bumped= 557 | bump_version __bumped 558 | eval "$1=$__bumped-dev.$__suffix" 559 | else 560 | eval "$1=$lastversion.$__suffix" 561 | fi 562 | fi 563 | } 564 | 565 | function init { 566 | git fetch > /dev/null 567 | TAGS="$(git tag)" 568 | IFS=$'\n' read -rd '' -a TAG_ARRAY <<<"$TAGS" 569 | 570 | get_latest ${TAG_ARRAY[@]} 571 | currentbranch="$(git rev-parse --abbrev-ref HEAD)" 572 | } 573 | 574 | case $ACTION in 575 | --help) 576 | echo -e "$HELP" 577 | ;; 578 | --version) 579 | echo -e "${PROG}: $PROG_VERSION" 580 | ;; 581 | final) 582 | init 583 | diff=$(git diff master | cat) 584 | if [ "$forcetag" == "false" ]; then 585 | if [ -n "$diff" ]; then 586 | echo "ERROR: Branch must be updated with master for final versions" 587 | exit 1 588 | fi 589 | fi 590 | increase_version 591 | ;; 592 | alpha|beta) 593 | init 594 | identifier="$ACTION" 595 | increase_version 596 | ;; 597 | candidate) 598 | init 599 | identifier="rc" 600 | increase_version 601 | ;; 602 | getlast) 603 | init 604 | echo "$lastversion" 605 | ;; 606 | getfinal) 607 | init 608 | echo "$finalversion" 609 | ;; 610 | getcurrent) 611 | init 612 | get_current current 613 | echo "$current" 614 | ;; 615 | get) 616 | init 617 | echo "Current final version: $finalversion" 618 | echo "Last tagged version: $lastversion" 619 | ;; 620 | *) 621 | echo "'$ACTION' is not a valid command, see --help for available commands." 622 | ;; 623 | esac 624 | -------------------------------------------------------------------------------- /helm_fallback.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/mumoshu/helm-x/pkg/cmdsite" 6 | "github.com/mumoshu/helm-x/pkg/helmx" 7 | "k8s.io/klog" 8 | "os" 9 | "os/exec" 10 | "regexp" 11 | "strings" 12 | "syscall" 13 | ) 14 | 15 | func helmFallback(r *helmx.Runner, args []string, err error) { 16 | errMsg := err.Error() 17 | if strings.HasPrefix(errMsg, `unknown command "`) { 18 | pattern := regexp.MustCompile(fmt.Sprintf(`unknown command "(.*)" for "%s"`, CommandName)) 19 | matches := pattern.FindSubmatch([]byte(errMsg)) 20 | if matches == nil || len(matches) != 2 { 21 | panic(fmt.Sprintf("Unexpected error returned from helm-x: %v", err)) 22 | } 23 | subcmdBytes := matches[1] 24 | subcmd := string(subcmdBytes) 25 | switch subcmd { 26 | case "completion", "create", "delete", "fetch", "get", "helm-git", "help", "history", "home", "init", "inspect", "list", "logs", "package", "plugin", "repo", "reset", "rollback", "search", "serve", "status", "test", "upgrade", "verify", "version": 27 | args = append([]string{r.HelmBin()}, args...) 28 | klog.V(1).Infof("helm-x: executing %s\n", strings.Join(args, " ")) 29 | helmBin, err := exec.LookPath(r.HelmBin()) 30 | if err != nil { 31 | klog.Errorf("%v", err) 32 | os.Exit(1) 33 | } 34 | execErr := syscall.Exec(helmBin, args, os.Environ()) 35 | if execErr != nil { 36 | panic(execErr) 37 | } 38 | case "dependency": 39 | args = append([]string{r.HelmBin()}, args...) 40 | klog.V(1).Infof("helm-x: executing %s\n", strings.Join(args, " ")) 41 | helmBin, err := exec.LookPath(r.HelmBin()) 42 | if err != nil { 43 | klog.Errorf("%v", err) 44 | os.Exit(1) 45 | } 46 | 47 | cs := cmdsite.New() 48 | cs.RunCmd = helmx.DefaultRunCommand 49 | 50 | _, stderr, err := cs.CaptureBytes(helmBin, args[1:]) 51 | if err != nil { 52 | if stderr != nil && strings.Contains(string(stderr), "Error: chart metadata (Chart.yaml) missing") { 53 | os.Exit(0) 54 | } else { 55 | fmt.Fprintln(os.Stderr, string(stderr)) 56 | os.Exit(1) 57 | } 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /install-binary.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/sh -e 3 | 4 | # Copied w/ love from the excellent hypnoglow/helm-s3 5 | 6 | if [ -n "${HELM_PUSH_PLUGIN_NO_INSTALL_HOOK}" ]; then 7 | echo "Development mode: not downloading versioned release." 8 | exit 0 9 | fi 10 | 11 | # initARch and initOS copied w/ love from https://github.com/technosophos/helm-template 12 | 13 | # initArch discovers the architecture for this system. 14 | initArch() { 15 | ARCH=$(uname -m) 16 | case $ARCH in 17 | armv5*) ARCH="armv5";; 18 | armv6*) ARCH="armv6";; 19 | armv7*) ARCH="armv7";; 20 | aarch64) ARCH="arm64";; 21 | x86) ARCH="386";; 22 | x86_64) ARCH="amd64";; 23 | i686) ARCH="386";; 24 | i386) ARCH="386";; 25 | esac 26 | } 27 | 28 | # initOS discovers the operating system for this system. 29 | initOS() { 30 | OS=$(echo `uname`|tr '[:upper:]' '[:lower:]') 31 | 32 | case "$OS" in 33 | # Msys support 34 | msys*) OS='windows';; 35 | # Minimalist GNU for Windows 36 | mingw*) OS='windows';; 37 | esac 38 | } 39 | 40 | source_dir=$(dirname $0) 41 | 42 | initArch 43 | initOS 44 | 45 | VER=$(awk '/version:/{gsub(/\"/,"", $2); print $2}' ${source_dir}/plugin.yaml) 46 | version=v${VER} 47 | #version="$(curl -s https://api.github.com/repos/mumoshu/helm-x/releases/latest | awk '/\"tag_name\":/{gsub( /[,\"]/,"", $2); print $2}')" 48 | echo "Downloading and installing helm-x ${version} ..." 49 | 50 | url="https://github.com/mumoshu/helm-x/releases/download/${version}/helm-x_${VER}_${OS}_${ARCH}.tar.gz" 51 | 52 | echo $url 53 | 54 | cd $HELM_PLUGIN_DIR 55 | mkdir -p "bin" 56 | mkdir -p "releases/${version}" 57 | 58 | # Download with curl if possible. 59 | if [ -x "$(which curl 2>/dev/null)" ]; then 60 | curl -sSL "${url}" -o "releases/${version}.tgz" 61 | else 62 | wget -q "${url}" -O "releases/${version}.tgz" 63 | fi 64 | 65 | find releases 66 | tar xzf "releases/${version}.tgz" -C "releases/${version}" 67 | mv "releases/${version}/helm-x" "bin/helm-x" || \ 68 | mv "releases/${version}/helm-x.exe" "bin/helm-x" 69 | rm -rf releases 70 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "github.com/variantdev/chartify" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | 13 | "github.com/spf13/cobra" 14 | "github.com/spf13/pflag" 15 | "k8s.io/client-go/tools/clientcmd" 16 | "k8s.io/klog" 17 | 18 | "github.com/mumoshu/helm-x/pkg/helmx" 19 | "github.com/mumoshu/helm-x/pkg/releasetool" 20 | 21 | "gopkg.in/yaml.v3" 22 | ) 23 | 24 | var Version string 25 | 26 | var CommandName = "helm-x" 27 | 28 | func main() { 29 | r := helmx.New() 30 | 31 | bin := r.HelmBin() 32 | helm3 := r.IsHelm3() 33 | 34 | r = helmx.New(helmx.HelmBin(bin), helmx.UseHelm3(helm3)) 35 | 36 | cmd := NewRootCmd(r) 37 | cmd.SilenceErrors = true 38 | 39 | // See https://flowerinthenight.com/blog/2019/02/05/golang-cobra-klog 40 | fs := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) 41 | // Suppress usage flag.ErrHelp 42 | fs.SetOutput(ioutil.Discard) 43 | klog.InitFlags(fs) 44 | args := append([]string{}, os.Args[1:]...) 45 | verbosityFromEnv := os.Getenv("HELM_X_VERBOSITY") 46 | if verbosityFromEnv != "" { 47 | // -v LEVEL must preceed the remaining args to be parsed by fs 48 | args = append([]string{"-v", verbosityFromEnv}, args...) 49 | } 50 | if err := fs.Parse(args); err != nil && err != flag.ErrHelp { 51 | fmt.Fprintln(os.Stderr, err) 52 | os.Exit(1) 53 | } 54 | remainings := fs.Args() 55 | pflag.CommandLine.AddGoFlagSet(fs) 56 | 57 | if err := cmd.Execute(); err != nil { 58 | helmFallback(r, remainings, err) 59 | klog.Fatalf("%v", err) 60 | } 61 | } 62 | 63 | func NewRootCmd(r *helmx.Runner) *cobra.Command { 64 | cmd := &cobra.Command{ 65 | Use: fmt.Sprintf("%s [apply|diff|template|dump|adopt]", CommandName), 66 | Short: "Turn Kubernetes manifests, Kustomization, Helm Chart into Helm release. Sidecar injection supported.", 67 | Long: ``, 68 | Version: Version, 69 | } 70 | 71 | out := cmd.OutOrStdout() 72 | 73 | cmd.AddCommand(NewApplyCommand(r, out, "apply", true)) 74 | cmd.AddCommand(NewApplyCommand(r, out, "upgrade", false)) 75 | cmd.AddCommand(NewDiffCommand(r, out)) 76 | cmd.AddCommand(NewTemplateCommand(r, out)) 77 | cmd.AddCommand(NewUtilDumpRelease(r, out)) 78 | cmd.AddCommand(NewAdopt(r, out)) 79 | 80 | return cmd 81 | } 82 | 83 | type dumpCmd struct { 84 | *helmx.ClientOpts 85 | 86 | TillerNamespace string 87 | 88 | Out io.Writer 89 | } 90 | 91 | // NewApplyCommand represents the apply command 92 | func NewApplyCommand(r *helmx.Runner, out io.Writer, cmdName string, installByDefault bool) *cobra.Command { 93 | upOpts := &helmx.UpgradeOpts{Out: out} 94 | pathOptions := clientcmd.NewDefaultPathOptions() 95 | 96 | cmd := &cobra.Command{ 97 | Use: fmt.Sprintf("%s [RELEASE] [DIR_OR_CHART]", cmdName), 98 | Short: "Install or upgrade the helm release from the directory or the chart specified", 99 | Long: `Install or upgrade the helm release from the directory or the chart specified 100 | 101 | Under the hood, this generates Kubernetes manifests from (1)directory containing manifests/kustomization/local helm chart or (2)remote helm chart, then inject sidecars, and finally install the result as a Helm release 102 | 103 | When DIR_OR_CHART is a local helm chart, this copies it into a temporary directory, renders all the templates into manifests by running "helm template", and then run injectors to update manifests, and install the temporary chart by running "helm upgrade --install". 104 | 105 | It's better than installing it with "kubectl apply -f", as you can leverage various helm sub-commands like "helm test" if you included tests in the "templates/tests" directory of the chart. 106 | It's also better in regard to security and reproducibility, as creating a helm release allows helm to detect Kubernetes resources removed from the desired state but still exist in the cluster, and automatically delete unnecessary resources. 107 | 108 | When DIR_OR_CHART is a local directory containing Kubernetes manifests, this copies all the manifests into a temporary directory, and turns it into a local Helm chart by generating a Chart.yaml whose version and appVersion are set to the value of the --version flag. 109 | 110 | When DIR_OR_CHART contains kustomization.yaml, this runs "kustomize build" to generate manifests, and then run injectors to update manifests, and install the temporary chart by running "helm upgrade --install". 111 | `, 112 | Args: func(cmd *cobra.Command, args []string) error { 113 | if len(args) != 2 { 114 | return errors.New("requires two arguments") 115 | } 116 | return nil 117 | }, 118 | RunE: func(cmd *cobra.Command, args []string) error { 119 | release := args[0] 120 | dir := args[1] 121 | 122 | tempLocalChartDir, err := r.Chartify(release, dir, upOpts.ChartifyOpts) 123 | if err != nil { 124 | cmd.SilenceUsage = true 125 | return err 126 | } 127 | 128 | if !upOpts.Debug { 129 | defer os.RemoveAll(tempLocalChartDir) 130 | } else { 131 | klog.Infof("helm chart has been written to %s for you to see. please remove it afterwards", tempLocalChartDir) 132 | } 133 | 134 | if len(upOpts.Adopt) > 0 { 135 | if err := r.Adopt( 136 | release, 137 | upOpts.Adopt, 138 | pathOptions, 139 | helmx.TillerNamespace(upOpts.TillerNamespace), 140 | helmx.Namespace(upOpts.Namespace), 141 | ); err != nil { 142 | return err 143 | } 144 | } 145 | 146 | if err := r.Upgrade(release, tempLocalChartDir, *upOpts); err != nil { 147 | cmd.SilenceUsage = true 148 | return err 149 | } 150 | 151 | return nil 152 | }, 153 | } 154 | f := cmd.Flags() 155 | 156 | upOpts.ChartifyOpts = chartifyOptsFromFlags(f) 157 | upOpts.ClientOpts = clientOptsFromFlags(f) 158 | 159 | //f.StringVar(&u.release, "name", "", "release name (default \"release-name\")") 160 | f.StringVar(&upOpts.Timeout, "timeout", "300", "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") 161 | 162 | f.BoolVar(&upOpts.DryRun, "dry-run", false, "simulate an upgrade") 163 | 164 | f.BoolVar(&upOpts.Install, "install", installByDefault, "install the release if missing") 165 | 166 | f.BoolVar(&upOpts.ResetValues, "reset-values", false, "reset the values to the ones built into the chart and merge in any new values") 167 | 168 | f.StringSliceVarP(&upOpts.Adopt, "adopt", "", []string{}, "adopt existing k8s resources before apply") 169 | 170 | f.StringVar(&pathOptions.LoadingRules.ExplicitPath, pathOptions.ExplicitFileFlag, pathOptions.LoadingRules.ExplicitPath, "use a particular kubeconfig file") 171 | 172 | return cmd 173 | } 174 | 175 | // NewTemplateCommand represents the template command 176 | func NewTemplateCommand(r *helmx.Runner, out io.Writer) *cobra.Command { 177 | templateOpts := &helmx.RenderOpts{Out: out} 178 | 179 | var release string 180 | 181 | cmd := &cobra.Command{ 182 | Use: "template [DIR_OR_CHART]", 183 | Short: "Print Kubernetes manifests that would be generated by `helm x apply`", 184 | Long: `Print Kubernetes manifests that would be generated by ` + "`helm x apply`" + ` 185 | 186 | Under the hood, this generates Kubernetes manifests from (1)directory containing manifests/kustomization/local helm chart or (2)remote helm chart, then inject sidecars, and finally print the resulting manifests 187 | 188 | When DIR_OR_CHART is a local helm chart, this copies it into a temporary directory, renders all the templates into manifests by running "helm template", and then run injectors to update manifests, and prints the results. 189 | 190 | When DIR_OR_CHART is a local directory containing Kubernetes manifests, this copies all the manifests into a temporary directory, and turns it into a local Helm chart by generating a Chart.yaml whose version and appVersion are set to the value of the --version flag. 191 | 192 | When DIR_OR_CHART contains kustomization.yaml, this runs "kustomize build" to generate manifests, and then run injectors to update manifests, and prints the results. 193 | `, 194 | Args: func(cmd *cobra.Command, args []string) error { 195 | if len(args) != 1 { 196 | return errors.New("requires one argument") 197 | } 198 | return nil 199 | }, 200 | RunE: func(cmd *cobra.Command, args []string) error { 201 | dir := args[0] 202 | 203 | tempLocalChartDir, err := r.Chartify(release, dir, templateOpts.ChartifyOpts) 204 | if err != nil { 205 | cmd.SilenceUsage = true 206 | return err 207 | } 208 | 209 | if !templateOpts.Debug { 210 | defer os.RemoveAll(tempLocalChartDir) 211 | } else { 212 | klog.Infof("helm chart has been written to %s for you to see. please remove it afterwards", tempLocalChartDir) 213 | } 214 | 215 | if err := r.Render(release, tempLocalChartDir, *templateOpts); err != nil { 216 | cmd.SilenceUsage = true 217 | return err 218 | } 219 | 220 | return nil 221 | }, 222 | } 223 | f := cmd.Flags() 224 | 225 | templateOpts.ChartifyOpts = chartifyOptsFromFlags(f) 226 | 227 | f.StringVar(&release, "name", "release-name", "release name (default \"release-name\")") 228 | f.BoolVar(&templateOpts.IncludeReleaseConfigmap, "include-release-configmap", false, "turn the result into a proper helm release, by removing hooks from the manifest, and including a helm release configmap/secret that should otherwise created by \"helm [upgrade|install]\"") 229 | f.BoolVar(&templateOpts.IncludeReleaseSecret, "include-release-secret", false, "turn the result into a proper helm release, by removing hooks from the manifest, and including a helm release configmap/secret that should otherwise created by \"helm [upgrade|install]\"") 230 | 231 | return cmd 232 | } 233 | 234 | // NewDiffCommand represents the diff command 235 | func NewDiffCommand(r *helmx.Runner, out io.Writer) *cobra.Command { 236 | diff := newDiffCommand(r, "diff", out) 237 | upgrade := newDiffCommand(r, "upgrade", out) 238 | diff.AddCommand(upgrade) 239 | return diff 240 | } 241 | 242 | func newDiffCommand(r *helmx.Runner, use string, out io.Writer) *cobra.Command { 243 | diffOpts := &helmx.DiffOpts{Out: out} 244 | 245 | cmd := &cobra.Command{ 246 | Use: fmt.Sprintf("%s [RELEASE] [DIR_OR_CHART]", use), 247 | Short: "Show a diff explaining what `helm x apply` would change", 248 | Long: `Show a diff explaining what ` + "`helm x apply`" + ` would change. 249 | 250 | Under the hood, this generates Kubernetes manifests from (1)directory containing manifests/kustomization/local helm chart or (2)remote helm chart, then inject sidecars, and finally print the resulting manifests 251 | 252 | When DIR_OR_CHART is a local helm chart, this copies it into a temporary directory, renders all the templates into manifests by running "helm template", and then run injectors to update manifests, and prints the results. 253 | 254 | When DIR_OR_CHART is a local directory containing Kubernetes manifests, this copies all the manifests into a temporary directory, and turns it into a local Helm chart by generating a Chart.yaml whose version and appVersion are set to the value of the --version flag. 255 | 256 | When DIR_OR_CHART contains kustomization.yaml, this runs "kustomize build" to generate manifests, and then run injectors to update manifests, and prints the results. 257 | `, 258 | Args: func(cmd *cobra.Command, args []string) error { 259 | if len(args) != 2 { 260 | return errors.New("requires two arguments") 261 | } 262 | return nil 263 | }, 264 | RunE: func(cmd *cobra.Command, args []string) error { 265 | release := args[0] 266 | dir := args[1] 267 | 268 | tempDir, err := r.Chartify(release, dir, diffOpts.ChartifyOpts) 269 | if err != nil { 270 | cmd.SilenceUsage = true 271 | return err 272 | } 273 | 274 | if diffOpts.Debug { 275 | klog.Infof("helm chart has been written to %s for you to see. please remove it afterwards", tempDir) 276 | } else { 277 | defer os.RemoveAll(tempDir) 278 | } 279 | 280 | changed, err := r.Diff(release, tempDir, diffOpts) 281 | if err != nil { 282 | cmd.SilenceUsage = true 283 | return err 284 | } 285 | if changed { 286 | os.Exit(2) 287 | } 288 | 289 | return nil 290 | }, 291 | } 292 | f := cmd.Flags() 293 | 294 | diffOpts.ChartifyOpts = chartifyOptsFromFlags(f) 295 | diffOpts.ClientOpts = clientOptsFromFlags(f) 296 | 297 | f.BoolVar(&diffOpts.AllowUnreleased, "allow-unreleased", false, "enables diffing of releases that are not yet deployed via Helm") 298 | f.BoolVar(&diffOpts.DetailedExitcode, "detailed-exitcode", false, "return a non-zero exit code when there are changes") 299 | f.BoolVar(&diffOpts.ResetValues, "reset-values", false, "reset the values to the ones built into the chart and merge in any new values") 300 | 301 | //f.StringVar(&u.release, "name", "", "release name (default \"release-name\")") 302 | 303 | return cmd 304 | } 305 | 306 | // NewAdopt represents the adopt command 307 | func NewAdopt(r *helmx.Runner, out io.Writer) *cobra.Command { 308 | adoptOpts := &helmx.AdoptOpts{Out: out} 309 | pathOptions := clientcmd.NewDefaultPathOptions() 310 | 311 | cmd := &cobra.Command{ 312 | Use: "adopt [RELEASE] [RESOURCES]...", 313 | Short: `Adopt the existing kubernetes resources as a helm release 314 | 315 | RESOURCES are represented as a whitespace-separated list of kind/name, like: 316 | 317 | configmap/foo.v1 secret/bar deployment/myapp 318 | 319 | So that the full command looks like: 320 | 321 | helm x adopt myrelease configmap/foo.v1 secret/bar deployment/myapp 322 | `, 323 | Args: func(cmd *cobra.Command, args []string) error { 324 | if len(args) < 2 { 325 | return errors.New("requires at least two argument") 326 | } 327 | return nil 328 | }, 329 | RunE: func(cmd *cobra.Command, args []string) error { 330 | release := args[0] 331 | resources := args[1:] 332 | 333 | return r.Adopt( 334 | release, 335 | resources, 336 | pathOptions, 337 | helmx.TillerNamespace(adoptOpts.TillerNamespace), 338 | helmx.Namespace(adoptOpts.Namespace), 339 | helmx.TillerStorageBackend(adoptOpts.TillerStorageBackend), 340 | ) 341 | }, 342 | } 343 | f := cmd.Flags() 344 | 345 | adoptOpts.ClientOpts = clientOptsFromFlags(f) 346 | 347 | f.StringVar(&adoptOpts.Namespace, "namespace", "", "The Namespace in which the resources to be adopted reside") 348 | 349 | f.StringVar(&pathOptions.LoadingRules.ExplicitPath, pathOptions.ExplicitFileFlag, pathOptions.LoadingRules.ExplicitPath, "use a particular kubeconfig file") 350 | 351 | return cmd 352 | } 353 | 354 | // NewDiffCommand represents the diff command 355 | func NewUtilDumpRelease(r *helmx.Runner, out io.Writer) *cobra.Command { 356 | dumpOpts := &dumpCmd{Out: out} 357 | 358 | cmd := &cobra.Command{ 359 | Use: "dump [RELEASE]", 360 | Short: "Dump the release object for developing purpose", 361 | Args: func(cmd *cobra.Command, args []string) error { 362 | if len(args) != 1 { 363 | return errors.New("requires one argument") 364 | } 365 | return nil 366 | }, 367 | RunE: func(cmd *cobra.Command, args []string) error { 368 | release := args[0] 369 | storage, err := releasetool.New(dumpOpts.TillerNamespace, releasetool.Opts{StorageBackend: dumpOpts.TillerStorageBackend}) 370 | if err != nil { 371 | return err 372 | } 373 | 374 | r, err := storage.GetDeployedRelease(release) 375 | if err != nil { 376 | return err 377 | } 378 | 379 | jsonBytes, err := json.Marshal(r) 380 | 381 | jsonObj := map[string]interface{}{} 382 | if err := json.Unmarshal(jsonBytes, &jsonObj); err != nil { 383 | return err 384 | } 385 | 386 | yamlBytes, err := yaml.Marshal(jsonObj) 387 | if err != nil { 388 | return err 389 | } 390 | 391 | fmt.Printf("%s\n", string(yamlBytes)) 392 | 393 | fmt.Printf("manifest:\n%s", jsonObj["manifest"]) 394 | 395 | return nil 396 | }, 397 | } 398 | f := cmd.Flags() 399 | 400 | dumpOpts.ClientOpts = clientOptsFromFlags(f) 401 | 402 | return cmd 403 | } 404 | 405 | func chartifyOptsFromFlags(f *pflag.FlagSet) *chartify.ChartifyOpts { 406 | chartifyOpts := &chartify.ChartifyOpts{} 407 | 408 | f.StringArrayVar(&chartifyOpts.Injectors, "injector", []string{}, "DEPRECATED: Use `--inject \"CMD ARG1 ARG2\"` instead. injector to use (must be pre-installed) and flags to be passed in the syntax of `'CMD SUBCMD,FLAG1=VAL1,FLAG2=VAL2'`. Flags should be without leading \"--\" (can specify multiple). \"FILE\" in values are replaced with the Kubernetes manifest file being injected. Example: \"--injector 'istioctl kube-inject f=FILE,injectConfigFile=inject-config.yaml,meshConfigFile=mesh.config.yaml\"") 409 | f.StringArrayVar(&chartifyOpts.Injects, "inject", []string{}, "injector to use (must be pre-installed) and flags to be passed in the syntax of `'istioctl kube-inject -f FILE'`. \"FILE\" is replaced with the Kubernetes manifest file being injected") 410 | f.StringArrayVar(&chartifyOpts.AdhocChartDependencies, "dependency", []string{}, "Adhoc dependencies to be added to the temporary local helm chart being installed. Syntax: ALIAS=REPO/CHART:VERSION e.g. mydb=stable/mysql:1.2.3") 411 | f.StringArrayVar(&chartifyOpts.JsonPatches, "json-patch", []string{}, "Kustomize JSON Patch file to be applied to the rendered K8s manifests. Allows customizing your chart without forking or updating") 412 | f.StringArrayVar(&chartifyOpts.StrategicMergePatches, "strategic-merge-patch", []string{}, "Kustomize Strategic Merge Patch file to be applied to the rendered K8s manifests. Allows customizing your chart without forking or updating") 413 | 414 | f.StringArrayVarP(&chartifyOpts.ValuesFiles, "values", "f", []string{}, "specify values in a YAML file or a URL (can specify multiple)") 415 | f.StringArrayVar(&chartifyOpts.SetValues, "set", []string{}, "set values on the command line (can specify multiple)") 416 | f.StringVar(&chartifyOpts.Namespace, "namespace", "", "Namespace to install the release into (only used if --install is set). Defaults to the current kube config Namespace") 417 | f.StringVar(&chartifyOpts.TillerNamespace, "tiller-namespace", "kube-system", "Namespace to in which release configmap/secret objects reside") 418 | f.StringVar(&chartifyOpts.ChartVersion, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used") 419 | 420 | f.BoolVar(&chartifyOpts.Debug, "debug", os.Getenv("HELM_X_DEBUG") == "on", "enable verbose output") 421 | f.BoolVar(&chartifyOpts.EnableKustomizeAlphaPlugins, "enable_alpha_plugins", false, "Enable the use of kustomize plugins") 422 | 423 | return chartifyOpts 424 | } 425 | 426 | func clientOptsFromFlags(f *pflag.FlagSet) *helmx.ClientOpts { 427 | clientOpts := &helmx.ClientOpts{} 428 | f.BoolVar(&clientOpts.TLS, "tls", false, "enable TLS for request") 429 | f.StringVar(&clientOpts.TLSCert, "tls-cert", "", "path to TLS certificate file (default: $HELM_HOME/cert.pem)") 430 | f.StringVar(&clientOpts.TLSKey, "tls-key", "", "path to TLS key file (default: $HELM_HOME/key.pem)") 431 | f.StringVar(&clientOpts.KubeContext, "kubecontext", "", "the kubeconfig context to use") 432 | f.StringVar(&clientOpts.TillerStorageBackend, "tiller-storage-backend", "configmaps", "the tiller storage backend to use. either `configmaps` or `secrets` are supported. See the upstream doc for more context: https://helm.sh/docs/install/#storage-backends") 433 | return clientOpts 434 | } 435 | -------------------------------------------------------------------------------- /pkg/cmdsite/cmdsite.go: -------------------------------------------------------------------------------- 1 | package cmdsite 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "k8s.io/klog" 7 | "os/exec" 8 | "strings" 9 | ) 10 | 11 | type RunCommand func(name string, args []string, stdout, stderr io.Writer, env map[string]string) error 12 | 13 | type CommandSite struct { 14 | RunCmd RunCommand 15 | 16 | Env map[string]string 17 | } 18 | 19 | func New() *CommandSite { 20 | return &CommandSite{ 21 | RunCmd: nil, 22 | Env: map[string]string{}, 23 | } 24 | } 25 | 26 | func (s *CommandSite) RunCommand(cmd string, args []string, stdout, stderr io.Writer) error { 27 | return s.RunCmd(cmd, args, stdout, stderr, s.Env) 28 | } 29 | 30 | func (r *CommandSite) CaptureStrings(binary string, args []string) (string, string, error) { 31 | stdout, stderr, err := r.CaptureBytes(binary, args) 32 | 33 | var so, se string 34 | 35 | if stdout != nil { 36 | so = string(stdout) 37 | } 38 | 39 | if stderr != nil { 40 | se = string(stderr) 41 | } 42 | 43 | return so, se, err 44 | } 45 | 46 | func (r *CommandSite) CaptureBytes(binary string, args []string) ([]byte, []byte, error) { 47 | klog.V(1).Infof("running %s %s", binary, strings.Join(args, " ")) 48 | _, err := exec.LookPath(binary) 49 | if err != nil { 50 | return nil, nil, err 51 | } 52 | 53 | var stdout, stderr bytes.Buffer 54 | err = r.RunCommand(binary, args, &stdout, &stderr) 55 | if err != nil { 56 | klog.V(1).Info(stderr.String()) 57 | } 58 | return stdout.Bytes(), stderr.Bytes(), err 59 | } 60 | -------------------------------------------------------------------------------- /pkg/helmx/adopt.go: -------------------------------------------------------------------------------- 1 | package helmx 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "strings" 10 | 11 | "github.com/pkg/errors" 12 | "k8s.io/client-go/tools/clientcmd" 13 | "k8s.io/helm/pkg/tiller/environment" 14 | 15 | "github.com/mumoshu/helm-x/pkg/releasetool" 16 | ) 17 | 18 | type AdoptOpts struct { 19 | *ClientOpts 20 | 21 | Namespace string 22 | TillerNamespace string 23 | 24 | Out io.Writer 25 | } 26 | 27 | type AdoptOption interface { 28 | SetAdoptOption(*AdoptOpts) error 29 | } 30 | 31 | // namespace returns the namespace of tiller 32 | // https://github.com/helm/helm/blob/a93ebe17d69e8bf99bdf4880acb40499653dd033/cmd/tiller/tiller.go#L256-L270 33 | func getTillerNamespace() string { 34 | if ns := os.Getenv("TILLER_NAMESPACE"); ns != "" { 35 | return ns 36 | } 37 | 38 | // Fall back to the namespace associated with the service account token, if available 39 | if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil { 40 | if ns := strings.TrimSpace(string(data)); len(ns) > 0 { 41 | return ns 42 | } 43 | } 44 | 45 | return environment.DefaultTillerNamespace 46 | } 47 | 48 | func getActiveContext(pathOptions *clientcmd.PathOptions) string { 49 | apiConfig, err := pathOptions.GetStartingConfig() 50 | if err != nil { 51 | return "" 52 | } 53 | if ctx, ok := apiConfig.Contexts[apiConfig.CurrentContext]; ok && ctx != nil { 54 | return ctx.Namespace 55 | } 56 | return "" 57 | } 58 | 59 | func (r *Runner) Adopt(release string, resources []string, pathOptions *clientcmd.PathOptions, opts ...AdoptOption) error { 60 | o := &AdoptOpts{} 61 | for i := range opts { 62 | if err := opts[i].SetAdoptOption(o); err != nil { 63 | return err 64 | } 65 | } 66 | 67 | tillerNs := o.TillerNamespace 68 | if tillerNs == "" { 69 | tillerNs = getTillerNamespace() 70 | } 71 | namespace := o.Namespace 72 | 73 | var storage *releasetool.ReleaseTool 74 | var err error 75 | switch o.TillerStorageBackend { 76 | case "configmaps": 77 | storage, err = releasetool.NewConfigMapBackedReleaseTool(tillerNs) 78 | if err != nil { 79 | return err 80 | } 81 | case "secrets": 82 | storage, err = releasetool.NewSecretBackednReleaseTool(tillerNs) 83 | if err != nil { 84 | return err 85 | } 86 | default: 87 | return errors.Errorf("unsupported tiller storage backend: %s", o.TillerStorageBackend) 88 | } 89 | 90 | kubectlArgs := []string{"get", "-o=json"} 91 | 92 | var ns string 93 | if namespace != "" { 94 | ns = namespace 95 | } else { 96 | ns = getActiveContext(pathOptions) 97 | if ns == "" { 98 | ns = "default" 99 | } 100 | } 101 | kubectlArgs = append(kubectlArgs, "-n="+ns) 102 | 103 | kubectlArgs = append(kubectlArgs, resources...) 104 | 105 | jsonData, err := r.Run("kubectl", kubectlArgs...) 106 | if err != nil { 107 | return err 108 | } 109 | 110 | var manifest string 111 | 112 | if len(resources) == 1 { 113 | item := map[string]interface{}{} 114 | 115 | if err := json.Unmarshal([]byte(jsonData), &item); err != nil { 116 | return err 117 | } 118 | 119 | yamlData, err := YamlMarshal(item) 120 | if err != nil { 121 | return err 122 | } 123 | 124 | item = export(item) 125 | 126 | yamlData, err = YamlMarshal(item) 127 | if err != nil { 128 | return err 129 | } 130 | 131 | metadata := item["metadata"].(map[string]interface{}) 132 | escaped := fmt.Sprintf("%s.%s", metadata["name"], strings.ToLower(item["kind"].(string))) 133 | manifest += fmt.Sprintf("\n---\n# Source: helm-x-dummy-chart/templates/%s.yaml\n", escaped) + string(yamlData) 134 | } else { 135 | type jsonVal struct { 136 | Items []map[string]interface{} `json:"items"` 137 | } 138 | v := jsonVal{} 139 | 140 | if err := json.Unmarshal([]byte(jsonData), &v); err != nil { 141 | return err 142 | } 143 | 144 | for _, item := range v.Items { 145 | yamlData, err := YamlMarshal(item) 146 | if err != nil { 147 | return err 148 | } 149 | 150 | item = export(item) 151 | 152 | yamlData, err = YamlMarshal(item) 153 | if err != nil { 154 | return err 155 | } 156 | 157 | metadata := item["metadata"].(map[string]interface{}) 158 | escaped := fmt.Sprintf("%s.%s", metadata["name"], strings.ToLower(item["kind"].(string))) 159 | manifest += fmt.Sprintf("\n---\n# Source: helm-x-dummy-chart/templates/%s.yaml\n", escaped) + string(yamlData) 160 | } 161 | } 162 | 163 | if manifest == "" { 164 | return fmt.Errorf("no resources to be adopted") 165 | } 166 | 167 | if err := storage.AdoptRelease(release, ns, manifest); err != nil { 168 | return err 169 | } 170 | 171 | return nil 172 | } 173 | -------------------------------------------------------------------------------- /pkg/helmx/chartify.go: -------------------------------------------------------------------------------- 1 | package helmx 2 | 3 | import "github.com/variantdev/chartify" 4 | 5 | func (r *Runner) Chartify(release, dirOrChart string, opts ...chartify.ChartifyOption) (string, error) { 6 | rr := chartify.New(chartify.HelmBin(r.helmBin), chartify.UseHelm3(r.isHelm3)) 7 | 8 | return rr.Chartify(release, dirOrChart, opts...) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/helmx/diff.go: -------------------------------------------------------------------------------- 1 | package helmx 2 | 3 | import ( 4 | "fmt" 5 | "github.com/variantdev/chartify" 6 | "github.com/mumoshu/helm-x/pkg/util" 7 | "io" 8 | "os/exec" 9 | ) 10 | 11 | type DiffOpts struct { 12 | *chartify.ChartifyOpts 13 | *ClientOpts 14 | 15 | Chart string 16 | 17 | kubeConfig string 18 | 19 | AllowUnreleased bool 20 | DetailedExitcode bool 21 | ResetValues bool 22 | 23 | Out io.Writer 24 | } 25 | 26 | /*func (o DiffOpts) GetSetValues() []string { 27 | return o.SetValues 28 | } 29 | 30 | func (o DiffOpts) GetValuesFiles() []string { 31 | return o.ValuesFiles 32 | } 33 | 34 | func (o DiffOpts) GetNamespace() string { 35 | return o.Namespace 36 | } 37 | 38 | func (o DiffOpts) GetKubeContext() string { 39 | return o.KubeContext 40 | } 41 | 42 | func (o DiffOpts) GetTLS() bool { 43 | return o.TLS 44 | } 45 | 46 | func (o DiffOpts) GetTLSCert() string { 47 | return o.TLSCert 48 | } 49 | 50 | func (o DiffOpts) GetTLSKey() string { 51 | return o.TLSKey 52 | } 53 | 54 | type diffOptsProvider interface { 55 | GetSetValues() []string 56 | GetValuesFiles() []string 57 | GetNamespace() string 58 | GetKubeContext() string 59 | GetTLS() bool 60 | GetTLSCert() string 61 | GetTLSKey() string 62 | } 63 | 64 | type diffOptsSetter struct { 65 | o diffOptsProvider 66 | } 67 | 68 | func (s *diffOptsSetter) SetDiffOption(o *DiffOpts) error { 69 | opts := s.o 70 | o.SetValues = opts.GetSetValues() 71 | o.ValuesFiles = opts.GetValuesFiles() 72 | o.Namespace = opts.GetNamespace() 73 | o.KubeContext = opts.GetKubeContext() 74 | o.TLS = opts.GetTLS() 75 | o.TLSCert = opts.GetTLSCert() 76 | o.TLSKey = opts.GetTLSKey() 77 | return nil 78 | } 79 | 80 | func WithDiffOpts(opts diffOptsProvider) DiffOption { 81 | return &diffOptsSetter{o: opts} 82 | }*/ 83 | 84 | type DiffOption interface { 85 | SetDiffOption(*DiffOpts) error 86 | } 87 | 88 | func (s *DiffOpts) SetDiffOption(o *DiffOpts) error { 89 | *o = *s 90 | return nil 91 | } 92 | 93 | // Diff returns true when the diff succeeds and changes are detected. 94 | func (r *Runner) Diff(release, chart string, opts ...DiffOption) (bool, error) { 95 | o := &DiffOpts{} 96 | 97 | for i := range opts { 98 | if err := opts[i].SetDiffOption(o); err != nil { 99 | return false, err 100 | } 101 | } 102 | 103 | var additionalFlags string 104 | additionalFlags += util.CreateFlagChain("context", []string{"3"}) 105 | if len(o.SetValues) > 0 { 106 | additionalFlags += util.CreateFlagChain("set", o.SetValues) 107 | } 108 | if len(o.ValuesFiles) > 0 { 109 | additionalFlags += util.CreateFlagChain("f", o.ValuesFiles) 110 | } 111 | if o.ResetValues { 112 | additionalFlags += util.CreateFlagChain("reset-values", []string{""}) 113 | } 114 | additionalFlags += util.CreateFlagChain("suppress-secrets", []string{""}) 115 | if o.Namespace != "" { 116 | additionalFlags += util.CreateFlagChain("namespace", []string{o.Namespace}) 117 | } 118 | if o.KubeContext != "" { 119 | additionalFlags += util.CreateFlagChain("kube-context", []string{o.KubeContext}) 120 | } 121 | if o.ChartVersion != "" { 122 | additionalFlags += util.CreateFlagChain("version", []string{o.ChartVersion}) 123 | } 124 | if o.TLS { 125 | additionalFlags += util.CreateFlagChain("tls", []string{""}) 126 | } 127 | if o.TLSCert != "" { 128 | additionalFlags += util.CreateFlagChain("tls-cert", []string{o.TLSCert}) 129 | } 130 | if o.TLSKey != "" { 131 | additionalFlags += util.CreateFlagChain("tls-key", []string{o.TLSKey}) 132 | } 133 | if o.AllowUnreleased { 134 | additionalFlags += util.CreateFlagChain("allow-unreleased", []string{""}) 135 | } 136 | if o.DetailedExitcode { 137 | additionalFlags += util.CreateFlagChain("detailed-exitcode", []string{""}) 138 | } 139 | 140 | command := fmt.Sprintf("%s diff upgrade %s %s%s", r.HelmBin(), release, chart, additionalFlags) 141 | if err := r.DeprecatedExec(command); err != nil { 142 | switch e := err.(type) { 143 | case *exec.ExitError: 144 | if e.ExitCode() == 2 { 145 | return true, nil 146 | } 147 | } 148 | return false, err 149 | 150 | } 151 | return false, nil 152 | } 153 | -------------------------------------------------------------------------------- /pkg/helmx/doc.go: -------------------------------------------------------------------------------- 1 | // helmx provides an API for other golang programs to use helm-x as a library 2 | package helmx 3 | -------------------------------------------------------------------------------- /pkg/helmx/helm3.go: -------------------------------------------------------------------------------- 1 | package helmx 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | ) 7 | 8 | func (r *Runner) IsHelm3() bool { 9 | if r.isHelm3 { 10 | return true 11 | } 12 | 13 | // Support explicit opt-in via environment variable 14 | if os.Getenv("HELM_X_HELM3") != "" { 15 | return true 16 | } 17 | 18 | // Autodetect from `helm verison` 19 | bytes, err := r.Run(r.HelmBin(), "version", "--client", "--short") 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | return strings.HasPrefix(string(bytes), "v3.") 25 | } 26 | -------------------------------------------------------------------------------- /pkg/helmx/helmx.go: -------------------------------------------------------------------------------- 1 | package helmx 2 | 3 | import ( 4 | "k8s.io/klog" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | type KustomizeOpts struct { 11 | Images []KustomizeImage `yaml:"images"` 12 | NamePrefix string `yaml:"namePrefix"` 13 | NameSuffix string `yaml:"nameSuffix"` 14 | Namespace string `yaml:"namespace"` 15 | } 16 | 17 | type KustomizeImage struct { 18 | Name string `yaml:"name"` 19 | NewName string `yaml:"newName"` 20 | NewTag string `yaml:"newTag"` 21 | Digest string `yaml:"digest"` 22 | } 23 | 24 | func (img KustomizeImage) String() string { 25 | res := img.Name 26 | if img.NewName != "" { 27 | res = res + "=" + img.NewName 28 | } 29 | if img.NewTag != "" { 30 | res = res + ":" + img.NewTag 31 | } 32 | if img.Digest != "" { 33 | res = res + "@" + img.Digest 34 | } 35 | return res 36 | } 37 | 38 | type ClientOpts struct { 39 | KubeContext string 40 | TLS bool 41 | TLSCert string 42 | TLSKey string 43 | 44 | TillerStorageBackend string 45 | } 46 | 47 | func export(item map[string]interface{}) map[string]interface{} { 48 | metadata := item["metadata"].(map[string]interface{}) 49 | if generateName, ok := metadata["generateName"]; ok { 50 | metadata["name"] = generateName 51 | } 52 | 53 | delete(metadata, "generateName") 54 | delete(metadata, "generation") 55 | delete(metadata, "resourceVersion") 56 | delete(metadata, "selfLink") 57 | delete(metadata, "uid") 58 | 59 | item["metadata"] = metadata 60 | 61 | delete(item, "status") 62 | 63 | return item 64 | } 65 | 66 | // DeprecatedExec takes a command as a string and executes it 67 | func (r *Runner) DeprecatedExec(cmd string) error { 68 | klog.Infof("running %s", cmd) 69 | args := strings.Split(cmd, " ") 70 | binary := args[0] 71 | _, err := exec.LookPath(binary) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | return r.commander.RunCommand(binary, args[1:], os.Stdout, os.Stderr) 77 | } 78 | 79 | // DeprecatedCaptureBytes takes a command as a string and executes it, and returns the captured stdout and stderr 80 | func (r *Runner) DeprecatedCaptureBytes(cmd string) ([]byte, []byte, error) { 81 | args := strings.Split(cmd, " ") 82 | binary := args[0] 83 | _, err := exec.LookPath(binary) 84 | if err != nil { 85 | return nil, nil, err 86 | } 87 | 88 | return r.CaptureBytes(binary, args[1:]) 89 | } 90 | 91 | func (r *Runner) CaptureBytes(binary string, args []string) ([]byte, []byte, error) { 92 | return r.commander.CaptureBytes(binary, args) 93 | } 94 | -------------------------------------------------------------------------------- /pkg/helmx/option.go: -------------------------------------------------------------------------------- 1 | package helmx 2 | 3 | type tillerNamespace struct { 4 | tillerNs string 5 | } 6 | 7 | func (n *tillerNamespace) SetAdoptOption(o *AdoptOpts) error { 8 | o.TillerNamespace = n.tillerNs 9 | return nil 10 | } 11 | 12 | func (n *tillerNamespace) SetDiffOption(o *DiffOpts) error { 13 | o.TillerNamespace = n.tillerNs 14 | return nil 15 | } 16 | 17 | func TillerNamespace(tillerNs string) *tillerNamespace { 18 | return &tillerNamespace{tillerNs: tillerNs} 19 | } 20 | 21 | var _ AdoptOption = &tillerNamespace{} 22 | var _ DiffOption = &tillerNamespace{} 23 | 24 | type namespace struct { 25 | ns string 26 | } 27 | 28 | func (n *namespace) SetAdoptOption(o *AdoptOpts) error { 29 | o.Namespace = n.ns 30 | return nil 31 | } 32 | 33 | func (n *namespace) SetDiffOption(o *DiffOpts) error { 34 | o.Namespace = n.ns 35 | return nil 36 | } 37 | 38 | func Namespace(ns string) *namespace { 39 | return &namespace{ns: ns} 40 | } 41 | 42 | var _ AdoptOption = &namespace{} 43 | var _ DiffOption = &namespace{} 44 | 45 | type storage struct { 46 | storage string 47 | } 48 | 49 | func (s *storage) SetAdoptOption(o *AdoptOpts) error { 50 | if o.ClientOpts == nil { 51 | o.ClientOpts = &ClientOpts{} 52 | } 53 | o.ClientOpts.TillerStorageBackend = s.storage 54 | return nil 55 | } 56 | 57 | func TillerStorageBackend(s string) *storage { 58 | return &storage{storage: s} 59 | } 60 | 61 | var _ AdoptOption = &storage{} 62 | -------------------------------------------------------------------------------- /pkg/helmx/render.go: -------------------------------------------------------------------------------- 1 | package helmx 2 | 3 | import ( 4 | "fmt" 5 | "github.com/variantdev/chartify" 6 | "github.com/mumoshu/helm-x/pkg/releasetool" 7 | "github.com/mumoshu/helm-x/pkg/util" 8 | "io" 9 | "strings" 10 | ) 11 | 12 | type RenderOpts struct { 13 | *chartify.ChartifyOpts 14 | 15 | IncludeReleaseConfigmap bool 16 | IncludeReleaseSecret bool 17 | 18 | Out io.Writer 19 | } 20 | 21 | // Render generates K8s manifests for the named release from the chart, and prints the resulting manifests to STDOUT 22 | func (r *Runner) Render(release, chart string, templateOpts RenderOpts) error { 23 | var additionalFlags string 24 | additionalFlags += util.CreateFlagChain("set", templateOpts.SetValues) 25 | additionalFlags += util.CreateFlagChain("f", templateOpts.ValuesFiles) 26 | if templateOpts.Namespace != "" { 27 | additionalFlags += util.CreateFlagChain("namespace", []string{templateOpts.Namespace}) 28 | } 29 | if release != "" { 30 | additionalFlags += util.CreateFlagChain("name", []string{release}) 31 | } 32 | if templateOpts.Debug { 33 | additionalFlags += util.CreateFlagChain("debug", []string{""}) 34 | } 35 | if templateOpts.ChartVersion != "" { 36 | additionalFlags += util.CreateFlagChain("--version", []string{templateOpts.ChartVersion}) 37 | } 38 | 39 | command := fmt.Sprintf("%s template %s%s", r.HelmBin(), chart, additionalFlags) 40 | stdout, stderr, err := r.DeprecatedCaptureBytes(command) 41 | if err != nil || len(stderr) != 0 { 42 | return fmt.Errorf(string(stderr)) 43 | } 44 | 45 | var output string 46 | 47 | if templateOpts.IncludeReleaseConfigmap || templateOpts.IncludeReleaseSecret { 48 | repoNameAndChart := strings.Split(chart, "/") 49 | 50 | chartWithoutRepoName := repoNameAndChart[len(repoNameAndChart)-1] 51 | 52 | ver := templateOpts.ChartVersion 53 | 54 | releaseManifests := []releasetool.ReleaseManifest{} 55 | 56 | if templateOpts.IncludeReleaseConfigmap { 57 | storage, err := releasetool.NewConfigMapBackedReleaseTool(templateOpts.TillerNamespace) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | releaseManifests = append(releaseManifests, storage.ReleaseToConfigMap) 63 | } 64 | 65 | if templateOpts.IncludeReleaseSecret { 66 | storage, err := releasetool.NewSecretBackednReleaseTool(templateOpts.TillerNamespace) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | releaseManifests = append(releaseManifests, storage.ReleaseToSecret) 72 | } 73 | 74 | output, err = releasetool.TurnHelmTemplateToInstall(chartWithoutRepoName, ver, templateOpts.TillerNamespace, release, templateOpts.Namespace, string(stdout), releaseManifests...) 75 | if err != nil { 76 | return err 77 | } 78 | } else { 79 | output = string(stdout) 80 | } 81 | 82 | fmt.Println(output) 83 | 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /pkg/helmx/run_cmd.go: -------------------------------------------------------------------------------- 1 | package helmx 2 | 3 | import ( 4 | "os/exec" 5 | "strings" 6 | 7 | "github.com/pkg/errors" 8 | "k8s.io/klog" 9 | ) 10 | 11 | func (r *Runner) runCommand(cmd *exec.Cmd) ([]byte, error) { 12 | cmdStr := strings.Join(cmd.Args, " ") 13 | klog.Info(cmdStr) 14 | outBytes, err := cmd.Output() 15 | if err != nil { 16 | exErr, ok := err.(*exec.ExitError) 17 | if !ok { 18 | return nil, errors.WithStack(err) 19 | } 20 | errOutput := string(exErr.Stderr) 21 | klog.Errorf("`%s` failed: %s", cmdStr, errOutput) 22 | return nil, errors.New(strings.TrimSpace(errOutput)) 23 | } 24 | // Trims off a single newline for user convenience 25 | output := outBytes 26 | outputLen := len(output) 27 | if outputLen > 0 && output[outputLen-1] == '\n' { 28 | output = output[0 : outputLen-1] 29 | } 30 | klog.V(1).Info(output) 31 | return output, nil 32 | } 33 | -------------------------------------------------------------------------------- /pkg/helmx/runner.go: -------------------------------------------------------------------------------- 1 | package helmx 2 | 3 | import ( 4 | "github.com/mumoshu/helm-x/pkg/cmdsite" 5 | "io" 6 | "os" 7 | "os/exec" 8 | "strings" 9 | ) 10 | 11 | type Runner struct { 12 | helmBin string 13 | isHelm3 bool 14 | commander *cmdsite.CommandSite 15 | } 16 | 17 | type Option func(*Runner) error 18 | 19 | func Commander(c cmdsite.RunCommand) Option { 20 | return func(r *Runner) error { 21 | r.commander.RunCmd = c 22 | return nil 23 | } 24 | } 25 | 26 | func HelmBin(b string) Option { 27 | return func(r *Runner) error { 28 | r.helmBin = b 29 | return nil 30 | } 31 | } 32 | 33 | func UseHelm3(u bool) Option { 34 | return func(r *Runner) error { 35 | r.isHelm3 = u 36 | return nil 37 | } 38 | } 39 | 40 | func New(opts ...Option) *Runner { 41 | cs := cmdsite.New() 42 | cs.RunCmd = DefaultRunCommand 43 | r := &Runner{ 44 | commander: cs, 45 | } 46 | for i := range opts { 47 | if err := opts[i](r); err != nil { 48 | panic(err) 49 | } 50 | } 51 | return r 52 | } 53 | 54 | func (r *Runner) HelmBin() string { 55 | if r.helmBin != "" { 56 | return r.helmBin 57 | } 58 | return os.Getenv("HELM_BIN") 59 | } 60 | 61 | func (r *Runner) Run(name string, args ...string) (string, error) { 62 | bytes, _, err := r.CaptureBytes(name, args) 63 | if err != nil { 64 | var out string 65 | if bytes != nil { 66 | out = string(bytes) 67 | } 68 | return out, err 69 | } 70 | return string(bytes), nil 71 | } 72 | 73 | func DefaultRunCommand(cmd string, args []string, stdout, stderr io.Writer, env map[string]string) error { 74 | command := exec.Command(cmd, args...) 75 | command.Stdout = stdout 76 | command.Stderr = stderr 77 | command.Env = mergeEnv(os.Environ(), env) 78 | return command.Run() 79 | } 80 | 81 | func mergeEnv(orig []string, new map[string]string) []string { 82 | wanted := env2map(orig) 83 | for k, v := range new { 84 | wanted[k] = v 85 | } 86 | return map2env(wanted) 87 | } 88 | 89 | func map2env(wanted map[string]string) []string { 90 | result := []string{} 91 | for k, v := range wanted { 92 | result = append(result, k+"="+v) 93 | } 94 | return result 95 | } 96 | 97 | func env2map(env []string) map[string]string { 98 | wanted := map[string]string{} 99 | for _, cur := range env { 100 | pair := strings.SplitN(cur, "=", 2) 101 | wanted[pair[0]] = pair[1] 102 | } 103 | return wanted 104 | } 105 | -------------------------------------------------------------------------------- /pkg/helmx/upgrade.go: -------------------------------------------------------------------------------- 1 | package helmx 2 | 3 | import ( 4 | "fmt" 5 | "github.com/variantdev/chartify" 6 | "github.com/mumoshu/helm-x/pkg/util" 7 | "io" 8 | ) 9 | 10 | type UpgradeOpts struct { 11 | *chartify.ChartifyOpts 12 | *ClientOpts 13 | 14 | Timeout string 15 | Install bool 16 | DryRun bool 17 | 18 | ResetValues bool 19 | 20 | kubeConfig string 21 | 22 | Adopt []string 23 | 24 | Out io.Writer 25 | } 26 | 27 | func (r *Runner) Upgrade(release, chart string, o UpgradeOpts) error { 28 | var additionalFlags string 29 | additionalFlags += util.CreateFlagChain("set", o.SetValues) 30 | additionalFlags += util.CreateFlagChain("f", o.ValuesFiles) 31 | timeout := o.Timeout 32 | if r.IsHelm3() { 33 | timeout = timeout + "s" 34 | } 35 | additionalFlags += util.CreateFlagChain("timeout", []string{fmt.Sprintf("%s", timeout)}) 36 | if o.Install { 37 | additionalFlags += util.CreateFlagChain("install", []string{""}) 38 | } 39 | if o.ResetValues { 40 | additionalFlags += util.CreateFlagChain("reset-values", []string{""}) 41 | } 42 | if o.Namespace != "" { 43 | additionalFlags += util.CreateFlagChain("namespace", []string{o.Namespace}) 44 | } 45 | if o.KubeContext != "" { 46 | additionalFlags += util.CreateFlagChain("kube-context", []string{o.KubeContext}) 47 | } 48 | if o.DryRun { 49 | additionalFlags += util.CreateFlagChain("dry-run", []string{""}) 50 | } 51 | if o.Debug { 52 | additionalFlags += util.CreateFlagChain("debug", []string{""}) 53 | } 54 | if o.TLS { 55 | additionalFlags += util.CreateFlagChain("tls", []string{""}) 56 | } 57 | if o.TLSCert != "" { 58 | additionalFlags += util.CreateFlagChain("tls-cert", []string{o.TLSCert}) 59 | } 60 | if o.TLSKey != "" { 61 | additionalFlags += util.CreateFlagChain("tls-key", []string{o.TLSKey}) 62 | } 63 | 64 | command := fmt.Sprintf("%s upgrade %s %s%s", r.HelmBin(), release, chart, additionalFlags) 65 | stdout, stderr, err := r.DeprecatedCaptureBytes(command) 66 | if err != nil || len(stderr) != 0 { 67 | return fmt.Errorf(string(stderr)) 68 | } 69 | fmt.Println(string(stdout)) 70 | 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /pkg/helmx/yaml.go: -------------------------------------------------------------------------------- 1 | package helmx 2 | 3 | import ( 4 | "bytes" 5 | "gopkg.in/yaml.v3" 6 | ) 7 | 8 | func YamlMarshal(v interface{}) ([]byte, error) { 9 | buf := &bytes.Buffer{} 10 | marshaller := yaml.NewEncoder(buf) 11 | marshaller.SetIndent(2) 12 | 13 | if err := marshaller.Encode(v); err != nil { 14 | return nil, err 15 | } 16 | 17 | return buf.Bytes(), nil 18 | } 19 | -------------------------------------------------------------------------------- /pkg/releasetool/doc.go: -------------------------------------------------------------------------------- 1 | // releasetool operates on Helm releases like ReleaseModule, but has only a few operations supported to make helm-x work. 2 | // For the upstream "ReleaseModule", see https://github.com/helm/helm/blob/53d432fa58748412ff3dc10bc27cbf996d96c3ed/pkg/tiller/release_modules.go 3 | package releasetool 4 | -------------------------------------------------------------------------------- /pkg/releasetool/helm_decode_release.go: -------------------------------------------------------------------------------- 1 | package releasetool 2 | 3 | // Copied and adopted with some modification from k8s.io/helm/pkg/storage/driver/util.go with love 4 | 5 | import ( 6 | "bytes" 7 | "compress/gzip" 8 | "encoding/base64" 9 | 10 | "github.com/golang/protobuf/proto" 11 | rspb "k8s.io/helm/pkg/proto/hapi/release" 12 | ) 13 | 14 | var b64 = base64.StdEncoding 15 | 16 | var magicGzip = []byte{0x1f, 0x8b, 0x08} 17 | 18 | // encodeRelease encodes a release returning a base64 encoded 19 | // gzipped binary protobuf encoding representation, or error. 20 | func encodeRelease(rls *rspb.Release) (string, error) { 21 | b, err := proto.Marshal(rls) 22 | if err != nil { 23 | return "", err 24 | } 25 | var buf bytes.Buffer 26 | w, err := gzip.NewWriterLevel(&buf, gzip.BestCompression) 27 | if err != nil { 28 | return "", err 29 | } 30 | if _, err = w.Write(b); err != nil { 31 | return "", err 32 | } 33 | w.Close() 34 | 35 | return b64.EncodeToString(buf.Bytes()), nil 36 | } 37 | -------------------------------------------------------------------------------- /pkg/releasetool/helm_makekey.go: -------------------------------------------------------------------------------- 1 | package releasetool 2 | 3 | // Copied with love from helm's pkg/storage/storage.go 4 | 5 | import "fmt" 6 | 7 | // makeKey concatenates a release name and version into 8 | // a string with format ```#v```. 9 | // This key is used to uniquely identify storage objects. 10 | func makeKey(rlsname string, version int32) string { 11 | return fmt.Sprintf("%s.v%d", rlsname, version) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/releasetool/helm_release.go: -------------------------------------------------------------------------------- 1 | package releasetool 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/golang/protobuf/ptypes/any" 8 | "k8s.io/helm/pkg/proto/hapi/chart" 9 | rspb "k8s.io/helm/pkg/proto/hapi/release" 10 | "k8s.io/helm/pkg/timeconv" 11 | "sigs.k8s.io/yaml" 12 | "strconv" 13 | "time" 14 | ) 15 | 16 | type ReleaseManifest func(release *rspb.Release, tillerNs string) (interface{}, error) 17 | 18 | func TurnHelmTemplateToInstall(chartName, version, tillerNs, releaseName, ns, manifest string, releaseManifests ...ReleaseManifest) (string, error) { 19 | man, hooks, err := SplitManifestAndHooks(manifest) 20 | if err != nil { 21 | return "", err 22 | } 23 | manifestData := []byte(base64.StdEncoding.EncodeToString([]byte(man))) 24 | vData := []byte(base64.StdEncoding.EncodeToString([]byte("This release is generated by helm-x"))) 25 | templates := []*chart.Template{ 26 | { 27 | Name: "templates/all.yaml", 28 | Data: manifestData, 29 | }, 30 | } 31 | if version == "" { 32 | version = "0.0.0" 33 | } 34 | c := &chart.Chart{ 35 | Metadata: &chart.Metadata{ 36 | Name: chartName, 37 | ApiVersion: "v1", 38 | AppVersion: version, 39 | Version: version, 40 | }, 41 | Templates: templates, 42 | Values: &chart.Config{Raw: "{}"}, 43 | Dependencies: []*chart.Chart{}, 44 | Files: []*any.Any{ 45 | { 46 | TypeUrl: "README.md", 47 | Value: vData, 48 | }, 49 | }, 50 | } 51 | 52 | if ns == "" { 53 | ns = "default" 54 | } 55 | 56 | ts := timeconv.Now() 57 | // See `kubectl get configmap -n kube-system -o jsonpath={.data.release} foo.v1 | base64 -D | gunzip -` for 58 | // real-world examples 59 | release := &rspb.Release{ 60 | Name: releaseName, 61 | Info: &rspb.Info{ 62 | FirstDeployed: ts, 63 | LastDeployed: ts, 64 | Status: &rspb.Status{ 65 | Code: rspb.Status_DEPLOYED, 66 | }, 67 | Description: fmt.Sprintf("Adopted with helm-x"), 68 | }, 69 | Chart: c, 70 | Config: &chart.Config{Raw: "{}"}, 71 | Manifest: man, 72 | Hooks: hooks, 73 | // Starts from "1". Try installing any chart and see by running `helm install --name foo yourchart && kubectl -n kube-system get configmap -o yaml foo.v1` 74 | Version: 1, 75 | Namespace: ns, 76 | } 77 | 78 | concatenated := man 79 | 80 | for _, m := range releaseManifests { 81 | releaseObj, err := m(release, tillerNs) 82 | if err != nil { 83 | return "", err 84 | } 85 | 86 | /// Turn the release object into JSON, and then YAML 87 | 88 | releaseJsonBytes, err := json.Marshal(releaseObj) 89 | if err != nil { 90 | return "", err 91 | } 92 | 93 | releaseYamlBytes, err := yaml.JSONToYAML(releaseJsonBytes) 94 | if err != nil { 95 | return "", err 96 | } 97 | 98 | concatenated = concatenated + "\n---\n" + string(releaseYamlBytes) 99 | } 100 | 101 | return concatenated, nil 102 | } 103 | 104 | func (s *ReleaseTool) BumpVersion(release *rspb.Release) (*rspb.Release, error) { 105 | latest, err := s.GetLatestRelease(release.Name) 106 | if err != nil { 107 | return nil, err 108 | } 109 | release.Version = latest.Version + 1 110 | 111 | return release, nil 112 | } 113 | 114 | func (s *ReleaseTool) ReleaseToConfigMap(release *rspb.Release, tillerNs string) (interface{}, error) { 115 | var err error 116 | release, err = s.BumpVersion(release) 117 | 118 | // Adopted from https://github.com/helm/helm/blob/90f50a11db5e81be0edd179b60a50adb9fcf3942/pkg/storage/driver/cfgmaps.go#L152-L164 with love 119 | var lbs labels 120 | 121 | lbs.init() 122 | lbs.set("CREATED_AT", strconv.Itoa(int(time.Now().Unix()))) 123 | 124 | key := makeKey(release.Name, release.Version) 125 | 126 | cfgmap, err := newConfigMapsObject(key, release, lbs) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | // Can't we automatically set these? 132 | cfgmap.APIVersion = "v1" 133 | cfgmap.Kind = "ConfigMap" 134 | cfgmap.Namespace = tillerNs 135 | 136 | return cfgmap, nil 137 | } 138 | 139 | func (s *ReleaseTool) ReleaseToSecret(release *rspb.Release, tillerNs string) (interface{}, error) { 140 | var err error 141 | release, err = s.BumpVersion(release) 142 | 143 | // Adopted from https://github.com/helm/helm/blob/90f50a11db5e81be0edd179b60a50adb9fcf3942/pkg/storage/driver/secrets.go#L152-L157 with love 144 | 145 | var lbs labels 146 | 147 | lbs.init() 148 | lbs.set("CREATED_AT", strconv.Itoa(int(time.Now().Unix()))) 149 | 150 | key := makeKey(release.Name, release.Version) 151 | 152 | cfgmap, err := newSecretsObject(key, release, lbs) 153 | if err != nil { 154 | return nil, err 155 | } 156 | 157 | // Can't we automatically set these? 158 | cfgmap.APIVersion = "v1" 159 | cfgmap.Kind = "Secret" 160 | cfgmap.Namespace = tillerNs 161 | 162 | return cfgmap, nil 163 | } 164 | -------------------------------------------------------------------------------- /pkg/releasetool/helm_release_cfgmap.go: -------------------------------------------------------------------------------- 1 | package releasetool 2 | 3 | // Copied from https://github.com/helm/helm/blob/90f50a11db5e81be0edd179b60a50adb9fcf3942/pkg/storage/driver/cfgmaps.go#L232 with love 4 | 5 | import ( 6 | "k8s.io/api/core/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | rspb "k8s.io/helm/pkg/proto/hapi/release" 9 | "strconv" 10 | ) 11 | 12 | func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*v1.ConfigMap, error) { 13 | const owner = "TILLER" 14 | 15 | // encode the release 16 | s, err := encodeRelease(rls) 17 | if err != nil { 18 | return nil, err 19 | } 20 | 21 | if lbs == nil { 22 | lbs.init() 23 | } 24 | 25 | // c labels 26 | lbs.set("NAME", rls.Name) 27 | lbs.set("OWNER", owner) 28 | lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)]) 29 | lbs.set("VERSION", strconv.Itoa(int(rls.Version))) 30 | 31 | // create and return configmap object 32 | return &v1.ConfigMap{ 33 | ObjectMeta: metav1.ObjectMeta{ 34 | Name: key, 35 | Labels: lbs.toMap(), 36 | }, 37 | Data: map[string]string{"release": s}, 38 | }, nil 39 | } 40 | -------------------------------------------------------------------------------- /pkg/releasetool/helm_release_labels.go: -------------------------------------------------------------------------------- 1 | package releasetool 2 | 3 | // Copied from k8s.io/helm/pkg/storage/driver/labels.go with love 4 | 5 | // labels is a map of key value pairs to be included as metadata in a configmap object. 6 | type labels map[string]string 7 | 8 | func (lbs *labels) init() { *lbs = labels(make(map[string]string)) } 9 | func (lbs labels) get(key string) string { return lbs[key] } 10 | func (lbs labels) set(key, val string) { lbs[key] = val } 11 | 12 | func (lbs labels) keys() (ls []string) { 13 | for key := range lbs { 14 | ls = append(ls, key) 15 | } 16 | return 17 | } 18 | 19 | func (lbs labels) match(set labels) bool { 20 | for _, key := range set.keys() { 21 | if lbs.get(key) != set.get(key) { 22 | return false 23 | } 24 | } 25 | return true 26 | } 27 | 28 | func (lbs labels) toMap() map[string]string { return lbs } 29 | 30 | func (lbs *labels) fromMap(kvs map[string]string) { 31 | for k, v := range kvs { 32 | lbs.set(k, v) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/releasetool/helm_release_secret.yaml.go: -------------------------------------------------------------------------------- 1 | package releasetool 2 | 3 | // Copied from https://github.com/helm/helm/blob/90f50a11db5e81be0edd179b60a50adb9fcf3942/pkg/storage/driver/secrets.go#L232 with love 4 | 5 | import ( 6 | "k8s.io/api/core/v1" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "strconv" 9 | 10 | rspb "k8s.io/helm/pkg/proto/hapi/release" 11 | ) 12 | 13 | func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*v1.Secret, error) { 14 | const owner = "TILLER" 15 | 16 | // encode the release 17 | s, err := encodeRelease(rls) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | if lbs == nil { 23 | lbs.init() 24 | } 25 | 26 | // apply labels 27 | lbs.set("NAME", rls.Name) 28 | lbs.set("OWNER", owner) 29 | lbs.set("STATUS", rspb.Status_Code_name[int32(rls.Info.Status.Code)]) 30 | lbs.set("VERSION", strconv.Itoa(int(rls.Version))) 31 | 32 | // create and return secret object 33 | return &v1.Secret{ 34 | ObjectMeta: metav1.ObjectMeta{ 35 | Name: key, 36 | Labels: lbs.toMap(), 37 | }, 38 | Data: map[string][]byte{"release": []byte(s)}, 39 | }, nil 40 | } 41 | -------------------------------------------------------------------------------- /pkg/releasetool/hooks.go: -------------------------------------------------------------------------------- 1 | package releasetool 2 | 3 | import ( 4 | "fmt" 5 | "gopkg.in/yaml.v2" 6 | "k8s.io/helm/pkg/hooks" 7 | "k8s.io/helm/pkg/proto/hapi/release" 8 | "strings" 9 | ) 10 | 11 | // See https://github.com/helm/helm/blob/29ab7a0a775ec7182be88a1b6daa9e65a472b46b/pkg/tiller/hooks.go#L35 12 | var events = map[string]release.Hook_Event{ 13 | hooks.PreInstall: release.Hook_PRE_INSTALL, 14 | hooks.PostInstall: release.Hook_POST_INSTALL, 15 | hooks.PreDelete: release.Hook_PRE_DELETE, 16 | hooks.PostDelete: release.Hook_POST_DELETE, 17 | hooks.PreUpgrade: release.Hook_PRE_UPGRADE, 18 | hooks.PostUpgrade: release.Hook_POST_UPGRADE, 19 | hooks.PreRollback: release.Hook_PRE_ROLLBACK, 20 | hooks.PostRollback: release.Hook_POST_ROLLBACK, 21 | hooks.ReleaseTestSuccess: release.Hook_RELEASE_TEST_SUCCESS, 22 | hooks.ReleaseTestFailure: release.Hook_RELEASE_TEST_FAILURE, 23 | hooks.CRDInstall: release.Hook_CRD_INSTALL, 24 | } 25 | 26 | type metadata struct { 27 | Name string `yaml:"name"` 28 | Annotations map[string]string `yaml:"annotations"` 29 | } 30 | 31 | type resource struct { 32 | Kind string `yaml:"kind"` 33 | Metadata metadata `yaml:"metadata"` 34 | } 35 | 36 | func SplitManifestAndHooks(manifest string) (string, []*release.Hook, error) { 37 | manifests := strings.Split(manifest, "\n---\n") 38 | 39 | resources := "" 40 | 41 | result := []*release.Hook{} 42 | 43 | for _, m := range manifests { 44 | m = strings.TrimPrefix(m, "---\n") 45 | 46 | lines := strings.Split(m, "\n") 47 | header := lines[0] 48 | 49 | items := strings.Split(header, "Source: ") 50 | 51 | if len(items) != 2 { 52 | return "", nil, fmt.Errorf("unexpected format of manifest: missing Source line:\n%s", m) 53 | } 54 | 55 | source := items[1] 56 | 57 | r := resource{Metadata: metadata{}} 58 | if err := yaml.Unmarshal([]byte(m), &r); err != nil { 59 | return "", nil, err 60 | } 61 | // See https://github.com/helm/helm/blob/2b36b1ad46278380aa70b8c190c346ce50e8dc96/pkg/hooks/hooks.go#L27 62 | hook := r.Metadata.Annotations[hooks.HookAnno] 63 | 64 | if hook == "" { 65 | resources += "\n---\n" + m 66 | continue 67 | } 68 | 69 | hookEvent, ok := events[hook] 70 | if !ok { 71 | return "", nil, fmt.Errorf("unexpected hook: %s", hook) 72 | } 73 | 74 | if r.Metadata.Name == "" { 75 | return "", nil, fmt.Errorf("assertion failed: expected metadata.name to be non-nil, but was nil: %+v", r) 76 | } 77 | 78 | rh := &release.Hook{ 79 | Name: r.Metadata.Name, 80 | Kind: r.Kind, 81 | Path: source, 82 | Manifest: strings.Join(lines[1:], "\n"), 83 | Events: []release.Hook_Event{hookEvent}, 84 | } 85 | 86 | result = append(result, rh) 87 | } 88 | 89 | return resources, result, nil 90 | } 91 | 92 | func ExtractHookManifests(manifest, target string) ([]string, error) { 93 | if target != "" { 94 | _, ok := events[target] 95 | 96 | if !ok { 97 | defined := []string{} 98 | for h, _ := range events { 99 | defined = append(defined, h) 100 | } 101 | return nil, fmt.Errorf("unknown target hook \"%s\": must be one of %s", target, strings.Join(defined, ", ")) 102 | } 103 | } 104 | 105 | manifests := strings.Split(manifest, "\n---\n") 106 | 107 | result := []string{} 108 | 109 | for _, m := range manifests { 110 | r := resource{Metadata: metadata{}} 111 | if err := yaml.Unmarshal([]byte(m), &r); err != nil { 112 | return nil, err 113 | } 114 | // See https://github.com/helm/helm/blob/2b36b1ad46278380aa70b8c190c346ce50e8dc96/pkg/hooks/hooks.go#L27 115 | hook := r.Metadata.Annotations[hooks.HookAnno] 116 | 117 | if hook == "" { 118 | continue 119 | } 120 | 121 | if target != "" && hook != target { 122 | continue 123 | } 124 | 125 | if _, exists := events[hook]; !exists { 126 | return nil, fmt.Errorf("unknown hook \"%s\" found. maybe a bug in helm-x?", hook) 127 | } 128 | 129 | result = append(result, m) 130 | } 131 | 132 | return result, nil 133 | } 134 | -------------------------------------------------------------------------------- /pkg/releasetool/releasetool.go: -------------------------------------------------------------------------------- 1 | package releasetool 2 | 3 | import ( 4 | "encoding/base64" 5 | "fmt" 6 | "github.com/golang/protobuf/ptypes/any" 7 | "k8s.io/helm/pkg/kube" 8 | "k8s.io/helm/pkg/proto/hapi/chart" 9 | rspb "k8s.io/helm/pkg/proto/hapi/release" 10 | "k8s.io/helm/pkg/storage" 11 | "k8s.io/helm/pkg/storage/driver" 12 | "k8s.io/helm/pkg/timeconv" 13 | 14 | // Required because go mod doesn't handle this transitive dep of helm 15 | // and results in `kube.New(nil).KubernetesClientSet()` to fail compiling on missing KubernetesClientSet() 16 | "k8s.io/kubernetes/pkg/kubectl/cmd/util" 17 | ) 18 | 19 | // ReleaseTool operates on Helm releases like ReleaseModule, but has only a few operations supported to make helm-x work. 20 | // For the upstream "ReleaseModule", see https://github.com/helm/helm/blob/53d432fa58748412ff3dc10bc27cbf996d96c3ed/pkg/tiller/release_modules.go 21 | type ReleaseTool struct { 22 | driver *storage.Storage 23 | } 24 | 25 | func goModWorkaround() error { 26 | return util.ErrExit 27 | } 28 | 29 | func NewSecretBackednReleaseTool(tillerNs string) (*ReleaseTool, error) { 30 | clientset, err := kube.New(nil).KubernetesClientSet() 31 | if err != nil { 32 | return nil, fmt.Errorf("Cannot initialize Kubernetes connection: %s", err) 33 | } 34 | 35 | secrets := driver.NewSecrets(clientset.CoreV1().Secrets(tillerNs)) 36 | 37 | storage := storage.Init(secrets) 38 | 39 | return &ReleaseTool{ 40 | driver: storage, 41 | }, nil 42 | } 43 | 44 | type Opts struct { 45 | StorageBackend string 46 | } 47 | 48 | func New(tillerNs string, opts ...Opts) (*ReleaseTool, error) { 49 | if len(opts) == 1 && opts[0].StorageBackend == "secrets" { 50 | return NewSecretBackednReleaseTool(tillerNs) 51 | } 52 | return NewConfigMapBackedReleaseTool(tillerNs) 53 | } 54 | 55 | func NewConfigMapBackedReleaseTool(tillerNs string) (*ReleaseTool, error) { 56 | clientset, err := kube.New(nil).KubernetesClientSet() 57 | if err != nil { 58 | return nil, fmt.Errorf("Cannot initialize Kubernetes connection: %s", err) 59 | } 60 | 61 | cmClient := clientset.CoreV1().ConfigMaps(tillerNs) 62 | cfgmaps := driver.NewConfigMaps(cmClient) 63 | 64 | storage := storage.Init(cfgmaps) 65 | 66 | return &ReleaseTool{ 67 | driver: storage, 68 | }, nil 69 | } 70 | 71 | func (s *ReleaseTool) GetLatestRelease(name string) (*rspb.Release, error) { 72 | return s.driver.Last(name) 73 | } 74 | 75 | func (s *ReleaseTool) AdoptRelease(name, ns, manifest string) error { 76 | manifestData := []byte(base64.StdEncoding.EncodeToString([]byte(manifest))) 77 | vData := []byte(base64.StdEncoding.EncodeToString([]byte("This release is generated by helm-x"))) 78 | c := &chart.Chart{ 79 | Metadata: &chart.Metadata{ 80 | Name: "helm-x-dummy-chart", 81 | ApiVersion: "v1", 82 | AppVersion: "0.1.0", 83 | }, 84 | Templates: []*chart.Template{ 85 | { 86 | Name: "templates/all.yaml", 87 | Data: manifestData, 88 | }, 89 | }, 90 | Values: &chart.Config{Raw: ""}, 91 | Dependencies: []*chart.Chart{}, 92 | Files: []*any.Any{ 93 | { 94 | TypeUrl: "README.md", 95 | Value: vData, 96 | }, 97 | }, 98 | } 99 | 100 | ts := timeconv.Now() 101 | // See `kubectl get configmap -n kube-system -o jsonpath={.data.release} foo.v1 | base64 -D | gunzip -` for 102 | // real-world examples 103 | release := &rspb.Release{ 104 | Chart: c, 105 | Info: &rspb.Info{ 106 | FirstDeployed: ts, 107 | LastDeployed: ts, 108 | Status: &rspb.Status{}, 109 | Description: fmt.Sprintf("Adopted with helm-x"), 110 | }, 111 | Config: &chart.Config{Raw: ""}, 112 | Manifest: manifest, 113 | } 114 | release.Name = name 115 | release.Namespace = ns 116 | release.Info.Status.Code = rspb.Status_DEPLOYED 117 | // Starts from "1". Try installing any chart and see by running `helm install --name foo yourchart && kubectl -n kube-system get configmap -o yaml foo.v1` 118 | release.Version = 1 119 | 120 | if err := s.driver.Create(release); err != nil { 121 | return err 122 | } 123 | 124 | return nil 125 | } 126 | 127 | func (s *ReleaseTool) GetDeployedRelease(name string) (*rspb.Release, error) { 128 | return s.driver.Deployed(name) 129 | } 130 | -------------------------------------------------------------------------------- /pkg/testcmdsite/testcmdsite.go: -------------------------------------------------------------------------------- 1 | package testcmdsite 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/mumoshu/helm-x/pkg/cmdsite" 7 | "github.com/spf13/pflag" 8 | "io" 9 | "os" 10 | ) 11 | 12 | type Command struct { 13 | ID int 14 | 15 | Args []string 16 | 17 | StringFlags map[string]string 18 | BoolFlags map[string]bool 19 | IntFlags map[string]int 20 | 21 | Stdout string 22 | Stderr string 23 | } 24 | 25 | type TestCommandSite struct { 26 | nextId int 27 | 28 | *cmdsite.CommandSite 29 | Commands map[string][]*Command 30 | } 31 | 32 | type Result struct { 33 | Stdout string 34 | Stderr string 35 | } 36 | 37 | func New() *TestCommandSite { 38 | tcs := &TestCommandSite{ 39 | Commands: map[string][]*Command{}, 40 | CommandSite: cmdsite.New(), 41 | } 42 | tcs.CommandSite.RunCmd = tcs.runCmd 43 | return tcs 44 | } 45 | 46 | func (s *TestCommandSite) Add(cmd string, flags map[string]interface{}, args []string, stdout, stderr string) (int, error) { 47 | c := &Command{ 48 | Args: args, 49 | StringFlags: map[string]string{}, 50 | BoolFlags: map[string]bool{}, 51 | IntFlags: map[string]int{}, 52 | Stdout: stdout, 53 | Stderr: stderr, 54 | } 55 | 56 | for n, v := range flags { 57 | switch typed := v.(type) { 58 | case string: 59 | c.StringFlags[n] = typed 60 | case int: 61 | c.IntFlags[n] = typed 62 | case bool: 63 | c.BoolFlags[n] = typed 64 | default: 65 | return -1, fmt.Errorf("unsupported flag type: value=%v, type=%T", typed, typed) 66 | } 67 | } 68 | 69 | id := s.nextId 70 | 71 | c.ID = id 72 | s.Commands[cmd] = append(s.Commands[cmd], c) 73 | 74 | s.nextId += 1 75 | 76 | fmt.Fprintf(os.Stderr, "after add: %v\n", *s) 77 | 78 | return id, nil 79 | } 80 | 81 | func (s *TestCommandSite) runCmd(cmd string, args []string, stdout, stderr io.Writer, env map[string]string) error { 82 | cmds, ok := s.Commands[cmd] 83 | if !ok { 84 | return fmt.Errorf("unexpected call to command \"%s\"", cmd) 85 | } 86 | 87 | for i := range cmds { 88 | c := cmds[i] 89 | 90 | fs := pflag.NewFlagSet(cmd, pflag.ContinueOnError) 91 | for f, v := range c.BoolFlags { 92 | _ = fs.Bool(f, false, "") 93 | fmt.Fprintf(os.Stderr, "expecting bool flag %s=%v\n", f, v) 94 | } 95 | for f, v := range c.StringFlags { 96 | _ = fs.String(f, "", "") 97 | fmt.Fprintf(os.Stderr, "expecting string flag %s=%v\n", f, v) 98 | } 99 | for f, v := range c.IntFlags { 100 | _ = fs.Int(f, 0, "") 101 | fmt.Fprintf(os.Stderr, "expecting int flag %s=%v\n", f, v) 102 | } 103 | 104 | a := append([]string{}, args...) 105 | 106 | fmt.Fprintf(os.Stderr, "parsing flags from: %v\n", a) 107 | 108 | if err := fs.Parse(a); err != nil { 109 | if i == len(cmds)-1 { 110 | return err 111 | } 112 | continue 113 | } 114 | 115 | a = fs.Args() 116 | 117 | fmt.Fprintf(os.Stderr, "remaining: %v\n", a) 118 | 119 | expectedArgs := map[string]struct{}{} 120 | actualArgs := map[string]struct{}{} 121 | 122 | for _, arg := range a { 123 | actualArgs[arg] = struct{}{} 124 | } 125 | for _, arg := range c.Args { 126 | expectedArgs[arg] = struct{}{} 127 | } 128 | 129 | for arg := range actualArgs { 130 | if _, ok := expectedArgs[arg]; !ok { 131 | return fmt.Errorf("unexpected arg: got=%v, expected=any of %v", arg, expectedArgs) 132 | } 133 | delete(expectedArgs, arg) 134 | } 135 | for arg := range expectedArgs { 136 | if _, ok := actualArgs[arg]; !ok { 137 | return fmt.Errorf("missing arg: expected=%v, got none", arg) 138 | } 139 | } 140 | 141 | for f, expected := range c.BoolFlags { 142 | actual, err := fs.GetBool(f) 143 | if err != nil { 144 | return err 145 | } 146 | if actual != expected { 147 | return fmt.Errorf("unexpected flag value: flag=%s, expected=%v, got=%v", f, expected, actual) 148 | } 149 | } 150 | for f, expected := range c.StringFlags { 151 | actual, err := fs.GetString(f) 152 | if err != nil { 153 | return err 154 | } 155 | if actual != expected { 156 | return fmt.Errorf("unexpected flag value: flag=%s, expected=%v, got=%v", f, expected, actual) 157 | } 158 | } 159 | for f, expected := range c.IntFlags { 160 | actual, err := fs.GetInt(f) 161 | if err != nil { 162 | return err 163 | } 164 | if actual != expected { 165 | return fmt.Errorf("unexpected flag value: flag=%s, expected=%v, got=%v", f, expected, actual) 166 | } 167 | } 168 | 169 | stdoutbuf := bufio.NewWriter(stdout) 170 | n, err := stdoutbuf.WriteString(c.Stdout) 171 | if err != nil { 172 | return err 173 | } 174 | if n != len(c.Stdout) { 175 | return fmt.Errorf("unable to write %d bytes", len(c.Stdout)-n) 176 | } 177 | if err := stdoutbuf.Flush(); err != nil { 178 | return err 179 | } 180 | 181 | stderrbuf := bufio.NewWriter(stderr) 182 | n, err = stderrbuf.WriteString(c.Stderr) 183 | if err != nil { 184 | return err 185 | } 186 | if n != len(c.Stderr) { 187 | return fmt.Errorf("unable to write %d bytes", len(c.Stderr)-n) 188 | } 189 | if err := stderrbuf.Flush(); err != nil { 190 | return err 191 | } 192 | 193 | return nil 194 | } 195 | 196 | return fmt.Errorf("invalid state") 197 | } 198 | -------------------------------------------------------------------------------- /pkg/testcmdsite/testcmdsite_test.go: -------------------------------------------------------------------------------- 1 | package testcmdsite 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTestCommandSite_RunCommand(t *testing.T) { 8 | s := New() 9 | 10 | s.Add("helm", map[string]interface{}{"install": true, "name": "myapp"}, []string{"upgrade", "stable/mychart"}, "ok", "ng") 11 | 12 | stdout, stderr, err := s.CaptureStrings("helm", []string{"--install", "--name", "myapp", "upgrade", "stable/mychart"}) 13 | if err != nil { 14 | t.Fatalf("unexpected error: %v", err) 15 | } 16 | 17 | if stdout != "ok" { 18 | t.Errorf("unexpected stdout: expected=%s, got=%s", "ok", stdout) 19 | } 20 | 21 | if stderr != "ng" { 22 | t.Errorf("unexpected stderr: expected=%s, got=%s", "ng", stderr) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "fmt" 4 | 5 | func CreateFlagChain(flag string, input []string) string { 6 | chain := "" 7 | dashes := "--" 8 | if len(flag) == 1 { 9 | dashes = "-" 10 | } 11 | 12 | for _, i := range input { 13 | if i != "" { 14 | i = " " + i 15 | } 16 | chain = fmt.Sprintf("%s %s%s%s", chain, dashes, flag, i) 17 | } 18 | 19 | return chain 20 | } 21 | -------------------------------------------------------------------------------- /plugin.yaml: -------------------------------------------------------------------------------- 1 | name: "x" 2 | version: "0.8.1" 3 | usage: "Turn Kubernetes manifests and Kustomization into Helm Release" 4 | description: "Turn Kubernetes manifests and Kustomization into Helm Release" 5 | command: "$HELM_PLUGIN_DIR/bin/helm-x" 6 | hooks: 7 | install: "$HELM_PLUGIN_DIR/install-binary.sh" 8 | update: "$HELM_PLUGIN_DIR/install-binary.sh" 9 | --------------------------------------------------------------------------------